💡 入门教程推荐:[https://ts.xcatliu.com/](https://ts.xcatliu.com/)
本文不是 TS 的入门教程,可以看成是 《TypeScript 入门教程 》的读书笔记,在后面使用 TS 的过程中,想不起来怎么用时,可以用来查询和翻阅的工具。
原始数据类型 JS 中包含的原始数据类型有:布尔值、数值、字符串、 null 、 undefined 以及 ES6 中的新类型 Symbol 和 ES10 中的新类型 BigInt 。
下面介绍下这些原始数据类型在 TS 中的应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const isDone : boolean = false ;const decLiteral : number = 6 ;const myName : string = 'Tom' ;const u : undefined = undefined ;const n : null = null ;const s : symbol = Symbol ('a' );const b : bigint = BigInt (10 );`` `</div>color3 注意:上面的用法是个错误❌例子,只是为了说明怎么显示的定义类型而作的说明,在真实的项目中不要这么使用。</div> 原因是 <font style="color:rgb(24, 32, 38);">TypeScript 能够根据参数、属性和变量的默认值或初始值推断其类型。无需显示的注释其类型。这样做会给代码增加不必要的冗长,使其更难以阅读,并且在某些情况下可能会阻止 TypeScript 推断更具体的类型。</font> <font style="color:rgb(24, 32, 38);"></font> <font style="color:rgb(24, 32, 38);">例如:</font>**<font style="color:rgb(24, 32, 38);">const isDone = false</font>**<font style="color:rgb(24, 32, 38);"> 的类型会自动推断为 </font>**<font style="color:rgb(24, 32, 38);">false</font>**<font style="color:rgb(24, 32, 38);">,</font>**<font style="color:rgb(24, 32, 38);">let isDone = false</font>**<font style="color:rgb(24, 32, 38);"> 的类型才会推断为 </font>**<font style="color:rgb(24, 32, 38);">boolean</font>**<font style="color:rgb(24, 32, 38);">,因此如果给 const 定义的 isDone 显示的注释其类型为 boolean 的化,会扩大它的类型范围,带来一些不必要的风险。</font> <font style="color:rgb(24, 32, 38);"></font> <font style="color:rgb(24, 32, 38);">因此只建议在定义一个变量没有初始值的情况下显示的定义其类型(</font><font style="color:#ED740C;">而且也是必须的</font><font style="color:rgb(24, 32, 38);">):</font></div>color3 <font style="color:rgb(51, 51, 51);">如果定义的时候没有赋值或者指定类型,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查</font></div> ` `` typescriptlet myFavoriteNumber; myFavoriteNumber = 'seven' ; myFavoriteNumber = 7 ;
在定义变量没有默认值的情况下,必须指定其类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let isDone : boolean ;let decLiteral : number ;let myName : string ;let u : undefined ;let n : null ;let s : symbol ;let b : bigint ;
注意,TypeScript 中使用字面量和构造函数定义的变量的类型是不一样的:
1 2 let bool1 : boolean = false ;let bool2 : Boolean = new Boolean (0 );
其他的基本类型都是一样,不再赘述。
void 和 never Javascript 中没有 void 和 never 的概念。在 TypeScript 中,其作用如下:
void表示没有任何返回值的函数
never表示那些永远不存的值的类型,例如那些总是会抛出异常或根本就不会有返回值的函数
void 应用场景:
1 2 3 function alertName ( ): void { alert ("My name is Tom" ); }
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null (只在 –strictNullChecks 未指定时)
1 let unusable : void = undefined ;
never 应用场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function error (message: string ): never { throw new Error (message); } function fail ( ) { return error ("Something failed" ); } function infiniteLoop ( ): never { while (true ) {} }
never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never。
对象类型 使用 interface 定义对象的类型:
1 2 3 4 5 6 7 8 9 interface Person { name : string ; age : number ; } let tom : Person = { name : "Tom" , age : 25 , };
上面的例子中,tom 的形状和接口 Person 必须一致,对象的元素不允许多也不允许少 ,赋值的时候变量的形状必须和接口的形状保持一致。
如何少一些字段:
使用可选属性 ?: ,但是依旧不允许添加未定义的属性 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface Person { name : string ; age?: number ; } let tom : Person = { name : "Tom" , }; let tom1 : Person = { name : "Tom" , age : 25 , gender : "male" , };
如何多一些字段:
可以定义一个任意属性,需要注意的是,一个接口只能定义一个任意属性 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 interface Person { name : string ; age?: number ; [propName : string ]: string ; } let tom : Person = { name : "Tom" , gender : "male" , }; let tom1 : Person = { name : "Tom" , age : 25 , gender : "male" , };
注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集 ,上例中,任意属性的值允许是 string ,但是可选属性 age 的值却是 number , number 不是 string 的子属性,所以报错了。
属性只读:
使用 readonly 定义的只读属性只能在创建的是否被赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface Person { readonly id : number ; name : string ; age?: number ; [propName : string ]: any ; } let tom : Person = { id : 89757 , name : "Tom" , gender : "male" , }; tom.id = 9527 ;
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
数组类型 在 TypeScript 中,定义数组类型的方式有多种,比较灵活。
1 2 3 4 5 6 7 8 9 10 11 let fibonacci : number [] = [1 , 1 , 2 , 3 , 5 ];let fibonacci : Array <number > = [1 , 1 , 2 , 3 , 5 ];interface NumberArray { [index : number ]: number ; } let fibonacci : NumberArray = [1 , 1 , 2 , 3 , 5 ];
函数类型 在 JavaScript 中,有两种常见的定义函数的方式 - 函数声明和函数表达式:
函数函数声明:
1 2 3 4 5 6 7 8 function sum (x, y ) { return x + y; } function sum (x: number , y: number ): number { return x + y; }
函数表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 let mySum = function (x, y ) { return x + y; }; let mySum = function (x: number , y: number ): number { return x + y; }; let mySum : (x: number , y: number ) => number = function ( x: number , y: number ): number { return x + y; }; interface SearchFunc { (source : string , subString : string ): boolean ; } let mySearch : SearchFunc = function (source: string , subString: string ) { return source.search (subString) !== -1 ; };
参数可选:
与接口中的可选属性类似,我们用 ? 表示可选的参数,需要注意的是: 可选参数后面不允许再出现必需参数了
1 2 3 4 5 6 7 8 9 function buildName (firstName: string , lastName?: string ) { if (lastName) { return firstName + " " + lastName; } else { return firstName; } } let tomcat = buildName ("Tom" , "Cat" );let tom = buildName ("Tom" );
参数默认值:
在 ES6 中,我们允许给函数的参数添加默认值, TypeScript 会将添加了默认值的参数识别为可选参数:
1 2 3 4 5 function buildName (firstName: string , lastName: string = "Cat" ) { return firstName + " " + lastName; } let tomcat = buildName ("Tom" , "Cat" );let tom = buildName ("Tom" );
此时就不受「可选参数必须接在必需参数后面」的限制了:
1 2 3 4 5 function buildName (firstName: string = "Tom" , lastName: string ) { return firstName + " " + lastName; } let tomcat = buildName ("Tom" , "Cat" );let cat = buildName (undefined , "Cat" );
剩余参数:
ES6 中,可以使用 <font style="color:rgb(51, 51, 51);">...rest</font> 的方式获取函数中的剩余参数(rest 参数):
1 2 3 4 5 6 7 8 function push (array, ...items ) { items.forEach (function (item ) { array.push (item); }); } let a : any [] = [];push (a, 1 , 2 , 3 );
事实上, <font style="color:rgb(51, 51, 51);">items </font>是一个数组。所以我们可以用数组的类型来定义它:
1 2 3 4 5 6 7 8 function push (array: any [], ...items: any [] ) { items.forEach (function (item ) { array.push (item); }); } let a = [];push (a, 1 , 2 , 3 );
函数重载:
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
比如,我们需要实现一个函数 reverse ,输入数字 123 的时候,输出反转的数字 321 ,输入字符串 ‘hello’ 的时候,输出反转的字符串 ‘olleh’ 。
利用联合类型,我们可以这么实现:
1 2 3 4 5 6 7 function reverse (x: number | string ): number | string | void { if (typeof x === "number" ) { return Number (x.toString ().split ("" ).reverse ().join ("" )); } else if (typeof x === "string" ) { return x.split ("" ).reverse ().join ("" ); } }
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个 reverse 的函数类型:
1 2 3 4 5 6 7 8 9 function reverse (x: number ): number ;function reverse (x: string ): string ;function reverse (x: number | string ): number | string | void { if (typeof x === "number" ) { return Number (x.toString ().split ("" ).reverse ().join ("" )); } else if (typeof x === "string" ) { return x.split ("" ).reverse ().join ("" ); } }
上例中,我们重复定义了多次函数 reverse ,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类类型 下面看一个使用类的例子:
1 2 3 4 5 6 7 8 9 10 11 class Greeter { greeting : string ; constructor (message: string ) { this .greeting = message; } greet ( ) { return "Hello, " + this .greeting ; } } let greeter = new Greeter ("world" );
继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Animal { name : string ; constructor (theName: string ) { this .name = theName; } move (distanceInMeters: number = 0 ) { console .log (`${this .name} moved ${distanceInMeters} m.` ); } } class Snake extends Animal { constructor (name: string ) { super (name); } move (distanceInMeters = 5 ) { console .log ("Slithering..." ); super .move (distanceInMeters); } } class Horse extends Animal { constructor (name: string ) { super (name); } move (distanceInMeters = 45 ) { console .log ("Galloping..." ); super .move (distanceInMeters); } } let sam = new Snake ("Sammy the Python" );let tom : Animal = new Horse ("Tommy the Palomino" );sam.move (); tom.move (34 );
公共,私有与受保护的修饰符:
public:默认的修饰符,允许在类的外部和派生类访问
private:不允许在声明它的类的外部访问
protected:允许在派生类中访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Person { protected name : string ; constructor (name: string ) { this .name = name; } } class Employee extends Person { private department : string ; constructor (name: string , department: string ) { super (name); this .department = department; } public getElevatorPitch ( ) { return `Hello, my name is ${this .name} and I work in ${this .department} .` ; } } let howard = new Employee ("Howard" , "Sales" );console .log (howard.getElevatorPitch ());console .log (howard.name );
**属性只读: **你可以使用 readonly 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
1 2 3 4 5 6 7 8 9 class Octopus { readonly name : string ; readonly numberOfLegs : number = 8 ; constructor (theName: string ) { this .name = theName; } } let dad = new Octopus ("Man with the 8 strong legs" );dad.name = "Man with the 3-piece suit" ;
静态属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Grid { static origin = { x : 0 , y : 0 }; calculateDistanceFromOrigin (point: { x: number ; y: number } ) { let xDist = point.x - Grid .origin .x ; let yDist = point.y - Grid .origin .y ; return Math .sqrt (xDist * xDist + yDist * yDist) / this .scale ; } constructor (public scale: number ) {} } let grid1 = new Grid (1.0 ); let grid2 = new Grid (5.0 ); console .log (grid1.calculateDistanceFromOrigin ({ x : 10 , y : 10 }));console .log (grid2.calculateDistanceFromOrigin ({ x : 10 , y : 10 }));
抽象类:
抽象类的特性如下:
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化
抽象类可以包含成员的实现细节
abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 abstract class Department { constructor (public name: string ) {} printName (): void { console .log ("Department name: " + this .name ); } abstract printMeeting (): void ; } class AccountingDepartment extends Department { constructor ( ) { super ("Accounting and Auditing" ); } printMeeting (): void { console .log ("The Accounting Department meets each Monday at 10am." ); } generateReports (): void { console .log ("Generating accounting reports..." ); } } let department : Department ; department = new Department (); department = new AccountingDepartment (); department.printName (); department.printMeeting (); department.generateReports ();
** **