在JavaScript(ES5)中仅支持通过函数和原型链继承模拟类的实现(用于抽象业务模型、组织数据结构并创建可重用组件),自 ES6 引入 class 关键字后,它才开始支持使用与Java类似的语法定义声明类。
TypeScript 作为 JavaScript 的超集,自然也支持 class 的全部特性,并且还可以对类的属性、方法等进行静态类型检测。
类
在实际业务中,任何实体都可以被抽象为一个使用类表达的类似对象的数据结构,且这个数据结构既包含属性,又包含方法,比如我们在下方抽象了一个猫的类。
1 2 3 4 5 6 7 8 9 10 11
| class Cat { name: string; constructor(name: string) { this.name = name; } meow() { console.log('Mew! Mew!'); } } const cat = new Cat('Q'); cat. meow();
|
首先,我们定义了一个 class Cat ,它拥有 string 类型的 name 属性、meow 方法和一个构造器函数。然后,我们通过 new 关键字创建了一个 Cat 的实例,并把实例赋值给变量 cat。最后,我们通过实例调用了类中定义的 meow 方法。如果使用传统的 JavaScript 代码定义类,我们需要使用函数+原型链的形式进行模拟,如下代码所示
1 2 3 4 5 6 7 8 9
| function Cat(name: string) { this.name = name; } Cat.prototype.meow = function () { console.log('Mew! Mew!'); };
const cat = new Cat('orange'); cat.meow();
|
我们定义了 Cat 类的构造函数,并在构造函数内部定义了 name 属性,通过 Cat 的原型链添加 meow 方法。和通过 class 方式定义类相比,这种方式明显麻烦不少,而且还缺少静态类型检测。因此,类是 TypeScript 编程中十分有用且不得不掌握的工具。
类的继承
在 TypeScript 中,使用 extends 关键字就能很方便地定义类继承的抽象模式,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Animal { type = 'Animal'; say(name: string) { console.log(`My name is ${name}~`); } } class Cat extends Animal { meow() { console.log('Mew! Mew!'); } } const cat= new Cat(); cat. meow(); cat.say('orange'); cat.type;
|
cat是派生类,它派生自定义的Animal基类,此时Cat实例继承了基类Animal的属性和方法。因此,实例 cat 支持 meow、say、type 等属性和方法。
这里Cat类中我们没有写构造函数,因为派生类如果包含一个构造函数,则必须在构造函数中调用 super() 方法,这是 TypeScript 强制执行的一条重要规则。如下,因为定义的 Cat 类构造函数中没有调用 super 方法,所以提示了一个 ts(2377) 的错误。
1 2 3 4 5 6 7 8 9
| class Cat extends Animal { name: string; constructor(name: string) { this.name = name; } meow() { console.log('Mew! Mew!'); } }
|
super 函数会调用基类的构造函数,如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Animal { age: number; type = 'Animal'; constructor(age: number) { this.age = age; } say(name: string) { console.log(`My name is ${name}~`); } }
class Cat extends Animal { name: string; constructor(name: string) { super(); this.name = name; }
meow() { console.log('Mew! Mew!'); } }
|
Animal 类的构造函数要求必须传入一个数字类型的 age 参数,而子类实际入参为空,所以提示了一个 ts(2554) 的错误。如果我们显式地给 super 函数传入一个 number 类型的值,比如说 super(1),则不会再提示错误了。
抽象类
抽象类是一种不能被实例化仅能被子类继承的特殊类。我们可以使用抽象类定义派生类需要实现的属性和方法,同时也可以定义其他被继承的默认属性和方法,如下代码所示:
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
| abstract class Multiplier { abstract a: number; abstract b: number; abstract multiply(): number; showName = 'Multiplier'; multiplyTwice(): number { return Math.pow((this.a * this.b),2); } }
class NumMultiplier extends Multiplier { a: number; b: number; constructor(a: number, b: number) { super(); this.a = a; this.b = b; } multiply(): number { return this.a * this.b; } }
const numMultiplier = new NumMultiplier(3, 5); console.log(numMultiplier.showName); console.log(numMultiplier.multiply()); console.log(numAdder.multiplyTwice());
|
通过 abstract 关键字,我们定义了一个抽象类Multiplier,并通过abstract关键字定义了抽象属性a、b及方法multiply,而且任何继承Multiplier的派生类都需要实现这些抽象属性和方法。因为抽象类不能被实例化,并且派生类必须实现继承自抽象类上的抽象属性和方法定义,所以抽象类的作用其实就是对基础逻辑的封装和抽象。