#

传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 class

C#Java里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。

# 类的概念

这里对类相关的概念做一个简单的介绍

  • 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
  • 对象(Object):类的实例,通过 new 生成
  • 面向对象(OOP)的三大特性:封装、继承、多态
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口
  • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应
  • 存取器(getter & setter):用以改变属性的读取和赋值行为
  • 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口
  • 实现(implements):接口可以被类实现。一个类只能继承自另一个类,但是可以实现多个接口

# 类的用法

这里简单快速的回顾一下类的用法

属性的方法

使用 class 定义类,使用 constructor 定义构造函数。

通过 new 生成新实例的时候,会自动调用构造函数。

class Person {
  public name;
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    return `My name is ${this.name}`;
  }
}

let a = new Person("Jack");
console.log(a.sayHi()); // My name is Jack

继承

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。

class Star extends Person {
  constructor(name) {
    super(name); // 调用父类的 constructor(name)
    console.log(this.name);
  }
  sayHi() {
    return "Hi, " + super.sayHi(); // 调用父类的 sayHi()
  }
}

let c = new Star("Iron Man"); // Iron Man
console.log(c.sayHi()); // Hi, My name is Iron Man

存取器

使用 getter 和 setter 可以改变属性的赋值和读取行为:

class Animal {
  constructor(name) {
    this.name = name;
  }
  get name() {
    return "Jack";
  }
  set name(value) {
    console.log("setter: " + value);
  }
}

let a = new Animal("Kitty"); // setter: Kitty
a.name = "Tom"; // setter: Tom
console.log(a.name);

静态方法

使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用

class Animal {
  static isAnimal(a) {
    return a instanceof Animal;
  }
}

let a = new Animal("Jack");
Animal.isAnimal(a); // true

# TypeScript 中类的用法

访问修饰符

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected

  • public 修饰的属性或方法是公有的
  • private 修饰的属性或方法是私有的
  • protected 修饰的属性或方法是受保护的

public 自不用说,很多时候,我们希望有的属性是无法直接存取的,这种情况我们可以使用private

class Animal {
  private name;
  public constructor(name) {
    this.name = name;
  }
}

let a = new Animal("Jack");
console.log(a.name);
a.name = "Tom";
// Property 'name' is private and only accessible within class 'Animal'.

当构造函数修饰为 private 时,该类不允许被继承或者实例化

class Animal {
  public name;
  private constructor(name) {
    this.name = name;
  }
}
class Cat extends Animal {
  constructor(name) {
    super(name);
  }
}

let a = new Animal("Jack");
// Constructor of class 'Animal' is private and only accessible within the class declaration.

参数属性

修饰符和 readonly 还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁

class Animal {
  public constructor(public name) {}
}

编译后

var Animal = /** @class */ (function () {
  function Animal(name) {
    this.name = name;
  }
  return Animal;
})();

readonly

只读属性关键字,只允许出现在属性声明或索引签名或构造函数中

class Animal {
  public readonly name;
  public constructor(name) {
    this.name = name;
  }
}

let a = new Animal("Jack");
console.log(a.name); // Jack
a.name = "Tom";
// Cannot assign to 'name' because it is a read-only property.

抽象类

abstract 用于定义抽象类和其中的抽象方法。

什么是抽象类?

首先,抽象类是不允许被实例化的

其次,抽象类中的抽象方法必须被子类实现

# 类的类型

给类加上 TypeScript 的类型很简单,就和用接口对一个对象定义类型一样

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayHi(): string {
    return `My name is ${this.name}`;
  }
}

let a: Animal = new Animal("Jack");
console.log(a.sayHi()); // My name is Jack