Ts类类型

在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(); // => 'Mew! Mew!'

首先,我们定义了一个 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(); // => 'Mew! Mew!'

我们定义了 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(); // => 'Mew! Mew!'
cat.say('orange'); // => My name is orange!
cat.type; // => Animal

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) {//派生类的构造函数必须包含 "super" 调用。ts(2377)
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(); // 应有 1 个参数,但获得 0 个。ts(2554)
   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); // => "Multiplier"
console.log(numMultiplier.multiply()); // => 15
console.log(numAdder.multiplyTwice()); // => 225

通过 abstract 关键字,我们定义了一个抽象类Multiplier,并通过abstract关键字定义了抽象属性a、b及方法multiply,而且任何继承Multiplier的派生类都需要实现这些抽象属性和方法。因为抽象类不能被实例化,并且派生类必须实现继承自抽象类上的抽象属性和方法定义,所以抽象类的作用其实就是对基础逻辑的封装和抽象。