对于传统的 JavaScript 程序我们会使用函数
和基于原型的继承
来创建可重用的组件,但对于熟悉使用面向对象方式的程序员使用这些语法就有些棘手,因为他们用的是基于类的继承
并且对象是由类构建出来的。 从 ECMAScript 2015,也就是 ES6 开始, JavaScript 程序员将能够使用基于类的面向对象的方式。 使用 TypeScript,我们允许开发者现在就使用这些特性,并且编译后的 JavaScript 可以在所有主流浏览器和平台上运行,而不需要等到下个 JavaScript 版本。
定义 类的声明:使用class
关键字
声明类的属性:在类的内部声明类的属性以及对应的类型
如果类型没有声明,那么它们默认是 any 的
也可以给属性设置初始化值
在默认的strictPropertyInitialization
模式下面我们的属性是必须 初始化的,如果没有初始化,那么编译时就会报错
如果在strictPropertyInitialization
模式下确实不希望给属性初始化,可以使用 name!: string
语法
类可以有自己的构造函数constructor
,通过new
关键字创建 一个实例时,构造函数会被调用
构造函数不需要返回任何值,默认返回当前创建出来的实例
类中可以有自己的函数,定义的函数称之为方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Person { name!: string ; age : number ; constructor (name : string , age : number ) { this .age = age; } running ( ) { console .log (this .name + " running!" ); } eating ( ) { console .log (this .name + " eating!" ); } }
继承
面向对象的其中一大特性就是继承,继承不仅仅可以减少代码量,也是多态的使用前提
使用extends
关键字来实现继承,子类中使用super
来访问父类
举例一下Student
类继承自Person
Student
类可以有自己的属性和方法,并且会继承Person
的属性和方法
在构造函数中,可以通过super
来调用父类的构造方法,对父类中的属性进行初始化
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 38 39 class Person { name!: string ; age : number ; constructor (name : string , age : number ) { this .name = name; this .age = age; } running ( ) { console .log (this .name + " running!" ); } eating ( ) { console .log (this .name + " eating!" ); } } class Student extends Person { sno : number ; constructor (name : string , age : number , sno : numebr ) { super (name, age); this .sno = sno; } studying ( ) { console .log (this .name + " studying!" ); } running ( ) { super .running (); console .log ("Student running!" ); } eating ( ) { console .log ("Student eating!" ); } }
多态 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class Animal { name : string ; constructor (name : string ) { this .name = name; } run (distance : number = 0 ) { console .log (`${this .name} run ${distance} m` ); } } class Snake extends Animal { constructor (name : string ) { super (name); } run (distance : number = 5 ) { console .log ("sliding..." ); super .run (distance); } } class Horse extends Animal { constructor (name : string ) { super (name); } run (distance : number = 50 ) { console .log ("dashing..." ); super .run (distance); } xxx ( ) { console .log ("xxx()" ); } } const snake = new Snake ("sn" );snake.run (); const horse = new Horse ("ho" );horse.run (); const tom : Animal = new Horse ("ho22" );tom.run (); const tom2 : Snake = new Animal ("tom2" );tom2.run (); const tom3 : Horse = new Animal ("tom3" );tom3.run ();
修饰符 在 TypeScript 中,类的属性和方法支持三种修饰符: public
、private
、protected
public
修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Person { protected name : string ; private age : number ; public gerder : string ; constructor (name : string , age : number , gender : string ) { this .name = name; this .age = age; this .gender = gender; } private eating ( ) { console .log ("吃东西" , this .age , this .name ); } } const p = new Person ("aaa" , 18 );class Student extends Person { constructor (name : string , age : number ) { super (name, age); } studying ( ) { console .log ("在学习" , this .name ); } } const stu = new Student ("bbb" , 18 );
readonly 你可以使用 readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化
1 2 3 4 5 6 7 8 9 class Person { readonly name : string = "abc" ; constructor (name : string ) { this .name = name; } } const person = new Person ("cba" );
参数属性 TypeScript 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性
这些被称为参数属性(parameter properties)
可以通过在构造函数参数前添加一个可见性修饰符 public
、private
、protected
或者 readonly
来创建参数属性,最后这些类属性字段也会得到这些修饰符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Person { constructor ( public name : string , private _age : number , readonly height : number , protected gender : string ) {} running ( ) { console .log (this ._age , "eating" ); } } const p = new Person ("aaa" , 18 , 1.88 , "male" );console .log (p.name , p.height );
存取器 在前面一些私有属性是不能直接访问的,或者某些属性想要监听它的获取(getter
)和设置(setter
)的过程,这个时候可以使用存取器
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 38 39 40 class Person { private _name : string ; private _age : number ; constructor (name : string , age : number ) { this ._name = name; this ._age = age; } running ( ) { console .log ("running:" , this ._name ); } set name (newValue : string ) { this ._name = newValue; } get name () { return this ._name ; } set age (newValue : number ) { if (newValue >= 0 && newValue < 200 ) { this ._age = newValue; } } get age () { return this ._age ; } } const p = new Person ("aaa" , 100 );p.name = "kobe" ; console .log (p.name ); p.age = -10 ; console .log (p.age );
静态属性 到目前为止,只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上,使用static
关键字
使用 类名.静态属性名
来访问静态属性
1 2 3 4 5 6 7 8 9 10 11 12 class Person { name1 : string = "A" ; static name2 : string = "B" ; } console .log (Person .name2 );console .log (new Person ().name1 );
抽象类 继承是多态使用的前提
所以在定义很多通用的调用接口时, 通常会让调用者传入父类,通过多态来实现更加灵活的调用方式
但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,可以定义为抽象方法
抽象方法:在 TypeScript 中没有具体实现的方法(没有方法体),就是抽象方法
抽象方法,必须存在于抽象类中
抽象类是使用abstract
声明的类
抽象类有如下的特点
抽象类做为其它派生类的基类使用
抽象类可以包含成员的实现细节
抽象类是不能被实例的话(也就是不能通过new
创建)
抽象方法必须被子类实现,则该类必须是一个抽象类
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 abstract class Shape { abstract getArea (): number ; } class Rectangle extends Shape { constructor (public width : number , public height : number ) { super (); } getArea ( ) { return this .width * this .height ; } } class Circle extends Shape { constructor (public radius : number ) { super (); } getArea ( ) { return this .radius ** 2 * Math .PI ; } } class Triangle extends Shape { constructor (public a : number , public b : number , public c : number ) { super (); } getArea ( ) { let p = 0 ; let s = 0 ; if (this .a + this .b < this .c || this .a + this .c < this .b || this .b + this .c < this .a ) { throw new Error ("不能构成三角形" ); } else { p = (this .a + this .b + this .c ) / 2 ; s = Math .sqrt (p * (p - this .a ) * (p - this .b ) * (p - this .c )); return s; } } } function calcArea (shape : Shape ) { return shape.getArea (); } calcArea (new Rectangle (10 , 20 ));calcArea (new Circle (5 ));calcArea (new Triangle (3 , 4 , 5 ));calcArea ({ getArea (): number { return 1 ; } });
类的类型 类本身也是可以作为一种数据类型的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Person { name : string ; constructor (name : string ) { this .name = name; } running ( ) { console .log (this .name + " running!" ); } } const p1 = new Person ("aaa" );const p2 : Person = { name : "bbb" , running ( ) { console .log (this .name + " running!" ); } };
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 class Person { constructor (public name : string , public age : number ) {} running ( ) {} } class Dog { constructor (public name : string , public age : number ) {} running ( ) {} } function printPerson (p : Person ) { console .log (p.name , p.age ); } printPerson (new Person ("why" , 18 ));printPerson ({ name : "kobe" , age : 30 , running : function ( ) {} });printPerson (new Dog ("旺财" , 3 ));const person : Person = new Dog ("果汁" , 5 );
对象类型属性修饰符 对象类型中的每个属性可以说明它的类型、属性是否可选、属性是否只读等信息
可选属性(Optional Properties)
只读属性(Readonly Properties)
在 TypeScript 中,属性可以被标记为 readonly
,这不会改变任何运行时的行为
但在类型检查的时候,一个标记为 readonly
的属性是不能被写入的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type IPerson = { name ?: string ; readonly age : number ; }; interface IKun { name ?: string ; readonly slogan : string ; } const p : IPerson = { name : "why" , age : 18 };
索引签名 索引签名的含义
有的时候,不能提前知道一个类型里的所有属性的名字,但是知道这些值的特征
这种情况,就可以用一个索引签名 (index signature) 来描述可能的值的类型
索引签名的用法
一个索引签名的属性类型必须是 string
或者是 number
虽然 TypeScript 可以同时支持 string
和 number
类型,但数字索引的返回类型一定要是字符索引返回类型的子类型
基本使用 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 interface InfoType { [key : string ]: string ; } function getInfo ( ): InfoType { const abc : any = "haha" ; return abc; } const info = getInfo ();console .log (info.name , info.age , info.address );interface ICollection { [index : number ]: string ; length : number ; } function printCollection (collection : ICollection ) { for (let i = 0 ; i < collection.length ; i++) { const item = collection[i]; console .log (item.length ); } } const array = ["abc" , "cba" , "nba" ];const tuple : [string , string ] = ["aaa" , "北京" ];printCollection (array);printCollection (tuple);
类型问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface IIndexType { }
两个签名 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 interface IIndexType { [index : number ]: string ; [key : string ]: any ; } const names : IIndexType = ["abc" , "cba" , "nba" ];const item1 = names[0 ];const forEachFn = names["forEach" ];names["aaa" ];
严格的字面量赋值检测 This PR implements stricter obiect literal assianment checks for the purpose of catching excess or misspelled properties.
The PR implements the suggestions in #3755. Specifically:
Every object literal is initially considered “fresh”
When a fresh object literal is assigned to a variable or passed for a parameter of a non-empty target type, it is an error for the object literal to specify properties that don’t exist in the target type.
Freshness disappears in a type assertion or when the type of an object literal is widened.
简单对上面的英文进行翻译解释
每个对象字面量最初都被认为是“新鲜的(fresh)”
当一个新的对象字面量分配给一个变量或传递给一个非空目标类型的参数时,对象字面量指定目标类型中不存在的属性是错误的
当类型断言或对象字面量的类型扩大时,新鲜度会消失
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 interface IPerson { name : string ; age : number ; } const obj = { name : "aaa" , age : 18 , height : 1.88 }; const info : IPerson = obj; function printPerson (person : IPerson ) {}const kobe = { name : "kobe" , age : 30 , height : 1.98 };printPerson (kobe); const obj2 = { name : "why" , age : 18 , height : 1.88 }; const p : IPerson = obj2;