类
class Dog {name: string;constructor(name: string) {this.name = name;}bark() {console.log("Woof! Woof!");}}const dog = new Dog("Q");dog.bark(); // => 'Woof! Woof!'
继承
和 js 一致,派生类如果包含一个构造函数,则必须在构造函数中调用 super() 方法。访问派生类的构造函数中的 “this” 前,必须调用 “super”。
class Animal {}class Dog extends Animal {name: string;constructor(name: string) {// ts(2377) Constructors for derived classes must contain a 'super' call.this.name = name;}bark() {console.log("Woof! Woof!");}}class Dog1 extends Animal {name: string;constructor(name: string) {super(); // 添加 super 方法this.name = name;}bark() {console.log("Woof! Woof!");}}
公共、私有与受保护的修饰符
在 TypeScript 中就支持 3 种访问修饰符,分别是 public、private、protected。
- public 修饰的是在任何地方可见、公有的属性或方法;
- private 修饰的是仅在同一类中可见、私有的属性或方法;
- protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法。
class Son {public firstName: string;private lastName: string = "Stark";constructor(firstName: string) {this.firstName = firstName;this.lastName; // ok}}const son = new Son("Tony");console.log(son.firstName); // => "Tony"son.firstName = "Jack";console.log(son.firstName); // => "Jack"console.log(son.lastName); // ts(2341) Property 'lastName' is private and only accessible within class 'Son'.
注意:TypeScript 中定义类的私有属性仅仅代表静态类型检测层面的私有。如果我们强制忽略 TypeScript 类型的检查错误,转译且运行 JavaScript 时依旧可以获取到 lastName 属性,这是因为 JavaScript 并不支持真正意义上的私有属性。
class Son {public firstName: string;protected lastName: string = "Stark";constructor(firstName: string) {this.firstName = firstName;this.lastName; // ok}}class GrandSon extends Son {constructor(firstName: string) {super(firstName);}public getMyLastName() {return this.lastName;}}const grandSon = new GrandSon("Tony");console.log(grandSon.getMyLastName()); // => "Stark"grandSon.lastName; // 属性“lastName”受保护,只能在类“Son”及其子类中访问。ts(2445)
只读修饰符
在前面的例子中,Son 类 public 修饰的属性既公开可见,又可以更改值,如果我们不希望类的属性被更改,则可以使用 readonly 只读修饰符声明类的属性,如下代码所示:
class Son {public readonly firstName: string;constructor(firstName: string) {this.firstName = firstName;}}const son = new Son("Tony");son.firstName = "Jack"; // 无法分配到 "firstName" ,因为它是只读属性。ts(2540)
注意:如果只读修饰符和可见性修饰符同时出现,我们需要将只读修饰符写在可见修饰符后面。
存取器
和 js 一致
class Son {public firstName: string;protected lastName: string = 'Stark';constructor(firstName: string) {this.firstName = firstName;}}class GrandSon extends Son {constructor(firstName: string) {super(firstName);}get myLastName() {return this.lastName;}set myLastName(name: string) {if (this.firstName === 'Tony') {this.lastName = name;} else {console.error('Unable to change myLastName');}}}const grandSon = new GrandSon('Tony');console.log(grandSon.myLastName); // => "Stark"grandSon.myLastName = 'Rogers';console.log(grandSon.myLastName); // => "Rogers"const grandSon1 = new GrandSon('Tony1');grandSon1.myLastName = 'Rogers'; // => "Unable to change myLastName"
静态属性
和 js 一致
class MyArray {static displayName = 'MyArray';static isArray(obj: unknown) {return Object.prototype.toString.call(obj).slice(8, -1) === 'Array';}}console.log(MyArray.displayName); // => "MyArray"console.log(MyArray.isArray([])); // => trueconsole.log(MyArray.isArray({})); // => false
基于静态属性的特性,我们往往会把与类相关的常量、不依赖实例 this 上下文的属性和方法定义为静态属性,从而避免数据冗余,进而提升运行性能。
注意:上边我们提到了不依赖实例 this 上下文的方法就可以定义成静态方法,这就意味着需要显式注解 this 类型才可以在静态方法中使用 this;非静态方法则不需要显式注解 this 类型,因为 this 的指向默认是类的实例
抽象类
接下来我们看看关于类的另外一个特性——抽象类,它是一种不能被实例化仅能被子类继承的特殊类。
我们可以使用抽象类定义派生类需要实现的属性和方法,同时也可以定义其他被继承的默认属性和方法,如下代码所示:
abstract class Adder {abstract x: number;abstract y: number;abstract add(): number;displayName = 'Adder';addTwice(): number {return (this.x + this.y) * 2;}}class NumAdder extends Adder {x: number;y: number;constructor(x: number, y: number) {super();this.x = x;this.y = y;}add(): number {return this.x + this.y;}}const numAdder = new NumAdder(1, 2);console.log(numAdder.displayName); // => "Adder"console.log(numAdder.add()); // => 3console.log(numAdder.addTwice()); // => 6
任何继承 Adder 的派生类都需要实现这些 abstract 的抽象属性和方法。
实际上,我们也可以定义一个描述对象结构的接口类型抽象类的结构,并通过 implements 关键字约束类的实现。
使用接口与使用抽象类相比,区别在于接口只能定义类成员的类型,如下代码所示:
接口不能定义其他被继承的默认属性和方法
1. interface IAdder {2. x: number;3. y: number;4. add: () => number;5. }6. class NumAdder implements IAdder {7. x: number;8. y: number;9. constructor(x: number, y: number) {10. this.x = x;11. this.y = y;12. }13. add() {14. return this.x + this.y;15. }16. addTwice() {17. return (this.x + this.y) * 2;18. }19. }
类的类型
类的最后一个特性——类的类型和函数类似,即在声明类的时候,其实也同时声明了一个特殊的类型(确切地讲是一个接口类型),这个类型的名字就是类名,表示类实例的类型;在定义类的时候,我们声明的除构造函数外所有属性、方法的类型就是这个特殊类型的成员。如下代码所示:
class A {name: string;constructor(name: string) {this.name = name;}}const a1: A = {}; // ts(2741) Property 'name' is missing in type '{}' but required in type 'A'.const a2: A = { name: 'a2' }; // ok
