TypeScript 是 JavaScript 的型別的超集,它可以編譯成純 JavaScript。編譯出來的 JavaScript 可以執行在任何瀏覽器上。TypeScript 編譯工具可以執行在任何伺服器和任何系統上。TypeScript 是開源的。
以下內容均出自於 TS入門教程
以及 Ts 官網的一些內容,沒有基礎的小夥伴直接看打了⭐️的內容即可。
嘗試
看了之後怎麼搭個環境寫一寫?
mkdir demo
cd demo
touch 1.ts
複製程式碼
開啟vscode,開啟控制檯,切換到問題 tab
歐了,開始嘗試 ts 吧
⭐️基礎型別
└boolean
let isDone: boolean = false;
// 使用建構函式 Boolean 創造的物件不是布林值
複製程式碼
└null & undefined
是所有型別的子型別
void型別不能賦值給 number
let u: undefined = undefined;
let n: null = null;
let num: number = undefined;
let u: undefined;
let num: number = u;
複製程式碼
└void 型別
一般表示函式沒有返回值。用在變數上沒有什麼卵用。
function warnUser(): void {
console.log("This is my warning message");
}
let a: void = undefined
let a: void = 'undefined' // 報錯,這是字串
複製程式碼
跟它相似的型別還有
undefined
和null
在不開啟嚴格空檢查的情況下--strictNullChecks
,他們可以賦值給所有已經定義過***其他型別***的變數。 也就是說他們是所有型別的子型別
let a: undefined = undefined
let a: null = null
複製程式碼
└數字 number
TypeScript裡的所有數字都是浮點數。 這些浮點數的型別是 number。支援十進位制和十六進位制字面量二進位制和八進位制字面量。
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二進位制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八進位制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
複製程式碼
└字串 string
單雙引
''
""
,模板字元的都被視為字串
let str:string = ''
複製程式碼
⭐️陣列型別
有多種宣告陣列的方式
- 最簡單的方法是使用
型別 + []
來表示陣列:
const arr: number[] = [1,2,3]
const arr2: string[] = ['1','2']
複製程式碼
- 陣列泛型定義方式
const arr2: Array<number> = [1,2,3,3]
const arr2: Array<string> = [1,2,3,3]
複製程式碼
- 介面表示陣列
interface NumArr {
[index: number]: number;
}
let numArr: NumArr = [1,2,3];
複製程式碼
- any 型別陣列
let list:any[] = [1,"z",{}]
複製程式碼
- 元組型別宣告
// 表示一個確定陣列長度和型別的寫法
const arr:[string,number] = ['2',3]
複製程式碼
- 類陣列
就是偽陣列的定義
官方已給了各自的定義介面 Arguments
, NodeList
, HTMLCollection
function sum() {
let args: IArguments = arguments;
}
複製程式碼
列舉 enum
js中沒有這型別,仿照強型別語言來的。值只能為數字,不定義預設值得情況為從0開始。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
// c = 1
enum Number {one = 10, two}
let c: Number = Number.two;
// c = 11
複製程式碼
⭐️any 型別(任意值)
指代所有的型別
let a: any = '123'
let a = 123; // 不宣告預設 any
複製程式碼
never
表示永遠不存在的值,一般會用來寫丟擲異常或推斷為返回值為never的函式。(比如return一個其他的never型別)
function error(message: string): never {
throw new Error(message);
}
error('a')
複製程式碼
object 型別
非簡單型別 也就是除number,string,boolean,symbol,null或undefined之外的型別。
function create(o: object | null): void{
console.log(o);
};
create({ prop: 0 }); // OK
create(null); // OK
create([]); // OK
create('a'); // error
複製程式碼
⭐️interface 介面
在 TypeScript 中,我們使用介面(Interfaces)來定義物件的型別。
對物件的描述
介面一般首字母大寫。
賦值的時候,變數必須和介面保持一致。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
複製程式碼
└可選屬性
不想完全匹配某個介面,通過
?
表示這個屬性是可選的仍然不允許新增未定義的屬性
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
複製程式碼
└任意屬性
讓介面允許新增任意的屬性值
[propName: string]: any;
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
複製程式碼
一旦定義了任意屬性, 那麼確定屬性和
?
可選屬性都必須是任意屬性的子集
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let p:Person = {
name: 'zzc',
age: 12, // error , 定義了 propName 必須將值設定為 string 型別
gender: 'male' ,
}
複製程式碼
└只讀屬性 readonly
相當於是常量了,初次賦值後不能重新賦值 做為變數使用的話用
const
,若做為屬性則使用readonly
。
interface demo {
readonly a: string; // readonly定以後不能改值
b: number
}
let obj: demo = {
a: 'ss',
b: 1
}
obj.a = 'aa' // error
obj.b = 2 // success
複製程式碼
只讀的約束存在於第一次給物件賦值的時候,而不是第一次給只讀屬性賦值的時候
interface Person {
readonly id: number;
}
const tom: Person = {} // error
tom.id = 1 // error,
複製程式碼
會報兩次錯,第一個是因為指定了 id,但沒有給 id 賦值
第二個錯是給只讀屬性id賦值了
└ReadonlyArray
通過
ReadonlyArray
定義的陣列,再也無法改變了。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = [1,2,3];
a[0] = 10 // success
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // 注意! 將readonly的值賦值給一個可變得陣列也是不行的。
a = ro as Array<any> // 但是可以用斷言重寫
複製程式碼
⭐️函式型別
常見的函式宣告方式有: 函式宣告 & 函式表示式
用 ts 定義函式要考慮它的輸入和輸出
- 函式宣告方式定義
function sum(a:number,b:number):number{
return a+b
}
// 形參和實引數量要一致
sum(1) // error
sum(1,2) //3
sum(1,2,3) // error
複製程式碼
- 函式表示式定義
// 方式 1
let sum = function(a:number,b:number):number {
return a + b;
}
// 方式二
let sum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
複製程式碼
方式一中只對等號右側的匿名函式定義了型別,左邊是ts通過型別推論定義出來的
方式二才是給 sum 定義型別,**其中的 =>
不是 es6的 =>
** ,它用來表示函式的定義,左邊是輸入型別,需要用括號括起來,右邊是輸出型別。
因為和 es6箭頭函式可能造成混淆,最好用方式一;
└可選引數
通過?
給函式定義可選引數
可選引數後面不允許再出現必須引數了
如果給引數新增了預設值,ts 會自動識別為可選,且不受上一條規則的限制。
function sum(a:number,b?:number){}
function sum(a?:number,b:number){} // error
function sum(a:number = 1,b:number){} // 預設值,識別為可選,且不報錯
複製程式碼
└...rest
使用…rest獲取剩餘引數,使用陣列型別去定義它
剩餘引數必須是函式的最後一個引數
function (a, ...items:any[]){}
function (...items:any[], a){} // error
複製程式碼
└函式的過載
過載允許一個函式接受不同數量或型別的引數時,作出不同的處理。
可以重複定義一個函式的型別
function say(somthing:string):string;
function say(somthing:number):string;
// 以上是函式定義
// 以下是函式實現
function say(somthing:string|number):string|number {
return somthing
}
複製程式碼
注意,TypeScript 會優先從最前面的函式定義開始匹配,所以多個函式定義如果有包含關係,需要優先把精確的定義寫在前面。
⭐️型別斷言
型別斷言(Type Assertion)可以用來手動指定一個值的型別。
斷定這個變數的型別是啥
型別斷言不是型別轉換
兩種寫法
<型別>值
or值 as 型別
如果在 tsx 語法中使用,必須用
as
- 例子
聯合型別可以指定一個變數為多種型別,此變數只能訪問型別們的共有方法。
但一些情況下我們必須使用某一型別的方法或屬性時,就可以用斷言
function say(something:number|string):void{
alert(something.length) // 聯合型別,報錯
}
// ==> 使用斷言, 在變數前加上 <型別>
function say(something:number|string):void{
alert( (<string>something).length ) // success
}
複製程式碼
斷言成一個聯合型別中不存在的型別是不允許的
function say(something:number|string):void{
alert(<boolean>something.length) // 聯合型別沒有 boolean ,error
}
複製程式碼
⭐️declare 宣告
第三方庫會暴露出一個變數,讓我們在專案中直接使用。
但是 ts 編譯時不知道這是啥,編譯無法通過。
此時我們就要用
declare var
宣告語句來定義他的型別
// 比如 jquery
$('div') // ERROR: Cannot find name 'jQuery'.
// ==> 使用 declare var 第三方庫變數: (引數: string) => 返回型別
declare var $: (selector: string) => any;
$('#foo'); // success
複製程式碼
declare var
並不是真正的宣告一個變數,編譯完會刪除,僅僅是定義型別。
└宣告檔案
通常我們會把宣告語句放到一個單獨的檔案(
*.d.ts
)中,這就是宣告檔案宣告檔案必需以
.d.ts
為字尾假如仍然無法解析,那麼可以檢查下
tsconfig.json
中的files
、include
和exclude
配置,確保其包含了jQuery.d.ts
檔案。
// src/jQuery.d.ts
declare var jQuery: (selector: string) => any;
複製程式碼
這只是非模組化專案中使用的例子
└第三方宣告檔案
當然,jQuery 的宣告檔案不需要我們定義了,社群已經幫我們定義好了:jQuery in DefinitelyTyped。
我們可以直接下載下來使用,但是更推薦的是使用 @types
統一管理第三方庫的宣告檔案。
@types
的使用方式很簡單,直接用 npm 安裝對應的宣告模組即可,以 jQuery 舉例:
npm i @types/jquery -D
複製程式碼
可以在這個頁面搜尋你需要的宣告檔案。
└自定義宣告檔案
宣告檔案有以下方法
- 全域性變數:通過
<script>
標籤引入第三方庫,注入全域性變數- npm 包:通過
import foo from 'foo'
匯入,符合 ES6 模組規範- UMD 庫:既可以通過
<script>
標籤引入,又可以通過import
匯入- 模組外掛:通過
import
匯入後,可以改變另一個模組的結構- 直接擴充套件全域性變數:通過
<script>
標籤引入後,改變一個全域性變數的結構。比如為String.prototype
新增了一個方法- 通過匯入擴充套件全域性變數:通過
import
匯入後,可以改變一個全域性變數的結構這裡只記錄 npm 匯入的方法, 其他請看 書寫宣告檔案
在給一個第三方庫寫宣告檔案之前,先檢視這個庫有沒有宣告檔案。一般來說,npm 包的宣告檔案可能存在於兩個地方:
- 跟 npm 包綁在一起,
package.json
中有type
欄位,或者有index.d.ts
宣告檔案。一般常用的包都有了,自己要釋出 npm 包的時候最好也繫結在一起。 - 釋出到了@types](microsoft.github.io/TypeSearch/) 裡,一般這種情況是作者沒寫,其他人寫了發上去的。
npm install @types/foo --save-dev
直接通過安裝。
如果都沒有,才自己寫。
宣告檔案存放的位置是有約束的,一般在兩個位置。
- 在
node_modules
建立第三方庫的宣告檔案,但這種一般不採納。一般node_modules
不會隨我們的應用釋出到伺服器|git上。 - 建立一個
types
目錄來寫,要配合tsconfig.json
來使用。
# 專案結構
├── README.md
├── src
| └── index.ts
├── types
| └── foo
| └── index.d.ts
└── tsconfig.json
複製程式碼
- 配置
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*" : ["types/*"]
}
}
}
複製程式碼
不管採用了以上兩種方式中的哪一種,我都強烈建議大家將書寫好的宣告檔案(通過給原作者發 pr,或者直接提交到
@types
裡)釋出到開源社群中,享受了這麼多社群的優秀的資源,就應該在力所能及的時候給出一些回饋。只有所有人都參與進來,才能讓 ts 社群更加繁榮。
- export
npm 包寫的宣告檔案 declare
不會宣告一個全域性變數,只有匯出的時候才會應用型別宣告。
export const name: string; // 簡單型別
export function getName(): string; // 函式
export class Animal { // class 宣告
constructor(name: string);
sayHi(): string;
}
export interface Options { // 介面
data: any;
}
// ===> 對應使用到專案中
import { name, getName, Animal, Directions, Options } from 'foo';
let myName = getName();
let cat = new Animal('Tom');
let options: Options = {
data: {
name: 'foo'
}
}
複製程式碼
- 混用
declare
export
通過 declare 定義多個變數,一次性匯出
declare const name: string;
declare function getName(): string;
declare class Animal {
constructor(name: string);
sayHi(): string;
}
export {
name,
getName,
Animal,
}
複製程式碼
- 匯出預設值
只有
function
、class
和interface
可以直接預設匯出,其他的變數需要先定義出來,再預設匯出針對預設匯出,一般會把匯出語句煩惱歌在宣告檔案的最前面。
export default function foo(): string;
export default interface Options {
data: any
}
export default class Person {
constructor(name: string);
sayHi(): string;
}
declare const str:string;
export default str;
複製程式碼
-
export namespace
namespace
本來是 TS 的模組化方案,隨著 es6越來越屌基本已經不在 ts 中使用了。但是宣告檔案中還是很常用的,表示變數是一個包含了子屬性的物件型別。
像是
lodash
,它是個物件,但提供了很多子屬性方法如lodash.debunce
如果物件擁有深層的層級,則需要用巢狀的
namespace
來宣告深層的屬性的型別:總的來說,用來匯出一個擁有子屬性的物件。
export namespace obj {
const name: string;
function fn(a:string,b?:nnumber):void;
class Event {
say(str:string):void
};
// 如果還有包含子屬性的物件,就巢狀
namespace sonObj {
const foo: string;
}
}
複製程式碼
└宣告合併
當一個變數,既是函式又是物件。可以組合多個語句宣告。
export function objAndFn(foo:string):any;
export namespace objAndFn {
function fn(boo:number):void;
const name:string;
}
複製程式碼
└針對 commonjs
規範
用以下方式來匯出:
// 整體匯出
module.exports = foo;
// 單個匯出
exports.bar = bar;
複製程式碼
在 ts 中,針對這種匯出,有多種方式可以匯入,第一種方式是 const ... = require
:
// 整體匯入
const foo = require('foo');
// 單個匯入
const bar = require('foo').bar;
複製程式碼
第二種方式是 import ... from
,注意針對整體匯出,需要使用 import * as
來匯入:
// 整體匯入
import * as foo from 'foo';
// 單個匯入
import { bar } from 'foo';
複製程式碼
第三種方式是 import ... require
,這也是 ts 官方推薦的方式:
// 整體匯入
import foo = require('foo');
// 單個匯入
import bar = require('foo').bar;
複製程式碼
對於 commonjs
規範的庫,需要使用 export = 變數
的語法寫宣告檔案
準確地講,export =
不僅可以用在宣告檔案中,也可以用在普通的 ts 檔案中。實際上,import ... require
和 export =
都是 ts 為了相容 AMD 規範和 commonjs 規範而創立的新語法
export = foo;
declare function foo():string;
declare namespace foo {
const bar: nnumber;
}
複製程式碼
⭐️內建物件
TS定義了 js 中內建物件的型別,在 TypeScript 核心庫的定義檔案中。
包括 ECMAscript、DOM、Bom 等
這些內建物件的型別在
.ts
中都可以直接使用
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
複製程式碼
還會在該內建物件中定位錯誤
Math.pow(10, '2'); // error 必須是兩個 number 型
document.addEventListener('click', function(e) {
console.log(e.targetCurrent);
// error, MouseEvent 型別不存在 targetCurrent屬性
});
複製程式碼
內建物件不包含 Node ,如果要使用
npm install @types/node --save-dev
複製程式碼
類
這章是介紹物件導向程式設計
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
console.log(this.greeting); // world
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
複製程式碼
繼承
基本繼承 作為基類的類一般被叫做超類,繼承的類叫做派生類
class Car {
name: string = '大眾';
by() {
console.log(this.name);
}
}
class Baoma extends Car {
name: string = '寶馬'
}
let bm = new Baoma()
bm.by()
複製程式碼
在子類
constructor
建構函式中***必須呼叫一下super***,它會執行基類的建構函式 在子類constructor
建構函式呼叫this之前必須先呼叫super
class FClass {
name: string = '超類'
constructor(name: string) { this.name = name; }
setAge(age:number) {
console.log(`${this.name} is ${age}`);
}
}
class SClass extends FClass {
constructor(name:string) {
super(name)
}
}
let f = new SClass('zzc')
f.setAge(20)
複製程式碼
公共,私有與受保護的修飾符
public
可以被除自己之外的所以子類訪問到 ts中所有成員預設為public
當一個成員被標記成private
時,無法被外部訪問 如果類的constructor
宣告成了private
那就沒辦法new
和繼承了。protected
受保護的,跟private
基本一樣,但它在子類中可以訪問;
class FClass {
name: string = '超類'
private constructor(name: string) { this.name = name; }
setAge(age:number) {
console.log(`${this.name} is ${age}`);
}
}
let f = new FClass() // error
class Son extends FClass {} // error
複製程式碼
protected 受保護的例子
class F {
protected name: string;
constructor(name: string) { this.name = name; }
setAge(age:number) {
console.log(`${this.name} is ${age}`);
}
}
class Son extends F {
constructor() {
super('son')
}
getName() {
super.name // 可以在子類訪問
}
}
let f = new F('super class')
let s = new Son()
console.log(s.name); // 不能在外部訪問
console.log(s.getName()); // 但可以通過子類的方法return出來獲取到
複製程式碼
在類中使用readonly
在類中或者
constructor
都還可以更改readonly
的值,但在外部就無法更改了。
class F {
readonly a:number = 8
constructor(age:number) {
this.a = age
}
}
let f = new F(9)
f.a = 10 // error 無法在外部更改
複製程式碼
- 引數屬性
在
constructor
用一句話定義並賦值一個屬性 只要在引數前面加了訪問限定符就可以直接給一個屬性直接賦值readonly
protected
public
private
static
是私有的,所以不能加。
class F {
readonly a:number = 8
constructor(readonly b:number) {
b = 10
}
}
let f = new F(9)
console.log(f); // {a,b}
複製程式碼
存取器 getter/setters
當一個屬性只有get方法的時候,它就是隻讀的。 這也是一種外部改變靜態屬性的方法
// 當a = 'a' 時,內部的_a才會等於賦的值,否則報錯。
class F {
private _a:string;
get a():string {
return this._a
}
set a(newA:string) {
if(newA === 'a') {
this._a = newA
}
else {
this._a = 'error'
}
}
}
let f = new F()
f.a = 'b'
console.log(f);
複製程式碼
靜態屬性 static
它只掛在class本身,而不是通過new例項化後出來的物件 所以你可以通過
類.static屬性
來呼叫,但不能用this
class F {
static num: number;
changeStatic() {
F.num = 19;
}
constructor () {
this.changeStatic()
console.log(F.num);
}
}
let f = new F();
複製程式碼
抽象類 & abstract
abstract
用來定義抽象類 和 在抽象類中定義抽象方法的 抽象類就是派生類的一個模板類,一般不會把它例項化,只是給子類繼承用的。
abstract class Animal { // 抽象一個Animal類
abstract makeSound(): void; // 抽象一個方法,必須在子類實現它
move(): void {
console.log('roaming the earch...');
}
constructor () {
}
}
class Son extends Animal {
constructor() {
super()
}
makeSound() { // 必須實現抽象類中的方法
return false
}
haha() {
console.log('error');
}
}
let s:Animal // 可以指定抽象類為一個型別
s.haha() // 如果上面的宣告瞭,那麼呼叫抽象類中不存在的haha方法是不允許
s = new Animal() // 不可以new 抽象類
s = new Son() // 正確
s.makeSound() // 正確
複製程式碼
⭐型別推論
如果沒有明確的指定型別,那麼 TypeScript 會依照型別推論(Type Inference)的規則推斷出一個型別。
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; // error
// 等價於 ==>
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
複製程式碼
如果定義的時候沒有賦值,不管之後有沒有賦值,都會被推斷成 any 型別而完全不被型別檢查:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
複製程式碼
⭐️聯合型別
表示變數可以是多種型別其中的一種
通過
|
分隔
let numOrStr : string | number;
numOrStr = 7;
numOrStr = '7';
numOrStr = true; // false
複製程式碼
當呼叫聯合型別的方法時,只能呼叫倆型別中共有的方法。
let numOrStr : string | number;
numOrStr.length // 報錯 length 不是 number 的方法
numOrStr.toString() // 可以
複製程式碼