Skip to content

TS 清单 - 类

ES6 之前,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。ES6 开始,我们终于迎来了class

先抛出几个和 类 相关的关键词:

  • class
  • extends / super
  • constructor
  • public / private / protected / readonly
  • get / set
  • static
  • abstract
  • new
  • interface

下面我们来看一个简单的 类 DEMO:

js
class Student {
  name: string
  constructor(name: string) {
    this.name = name
  }
  sayHello() {
    return `${this.name} say hello for you.`
  }
}
let stu1 = new Student('Bob')
console.log(stu1.sayHello()) // print: Bob say hello for you.

一、继承:(涉及关键词 class / extends / super / constructor / new)

js
// 1. 类与类之间的继承用关键词 extends 来定义;
// 2. 子类(如果有constructor的具体实现时),必须在 constructor 的第一行代码中调用 super(),super里面的参数根据父类的constructor来确定;
// 3. 子类 new 出来的实例,可以访问 自身和父类 的属性以及方法
class Person {
  name: string
  age: number
  constructor(age: number) {
    this.age = age
  }
  move() {
    return `${this.name} is move.`
  }
}
class Student extends Person {
  constructor(name: string, age: number) {
    super(age)
    this.name = name
  }
  sayHello() {
    return `${this.name} say hello for you.`
  }
}
let stu1 = new Student('Bob', 28)
console.log(stu1.sayHello()) // print: Bob say hello for you.
console.log(stu1.move()) // print: Bob is move.

二、公共,私有与受保护的修饰符等:(涉及关键词 public / private / protected / readonly)

在 TypeScript 里,成员都默认为 public。

js
// 1. 类的成员(属性或方法)默认是 public 的;
class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    move () {
        return `${this.name} is move.`
    }
}
// 等价于
class Person {
    public name: string;
    public age: number;
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    public move () {
        return `${this.name} is move.`
    }
}

let p1 = new Person('Bob', 28)
console.log(p1.move())            // print: Bob is move.

当成员被标记成 private 时,它就不能在声明它的类的外部访问。

js
class Person {
    private name: string;
    constructor(name: string) {
        this.name = name;
    }
    // 可以通过方法暴露相关私有成员
    getName () {
        return this.name
    }
}

let p1 = new Person('Bob')
// console.log(p1.name)        // 报错提示: 属性“name”为私有属性,只能在类“Person”中访问。
console.log(p1.getName())      // print: Bob

protected 修饰符与 private 修饰符的行为很相似,但有一点不同, protected 成员在派生类中仍然可以访问。
构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。

js
class Person {
    protected name: string;
    protected constructor(name: string) {
        this.name = name
    }
}
// let p1 = new Person('Bob')     // 报错提示:类“Person”的构造函数是受保护的,仅可在类声明中访问。
class Employee extends Person {
    constructor(name: string) {
        super(name)
    }
    getName () {
        return this.name
    }
}
let p2 = new Employee('Bob')    // 只能通过 public 的类进行实例化
console.log(p2.getName())       // Bob

readonly 关键字是将属性设置为只读的:

js
class Person {
    readonly name: string;
    constructor(name: string) {
        this.name = name
    }
}
let p1 = new Person('Bob')
console.log(p1.name)          // print: Bob
// p1.name = 'BoBo'           // 报错提示:无法为“name”赋值,因为它是只读属性。

三、存取器: (关键词 get / set)

TypeScript 支持通过 getters/setters 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

js
let passcode = "secret passcode";
class Employee {
    private _fullName: string;
    get fullName(): string {
        return this._fullName;
    }
    set fullName(newName: string) {
        // 这里是针对 set 做了一层拦截,满足条件的情况下才能正常设置信息
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        } else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

四、静态属性: (关键词 static)

静态属性,就是存在于 类本身 上的属性,而不是类创造出来的实例上的属性。

js
class Person {
  static desc: string = '人类'
  name: string
  constructor(name: string) {
    this.name = name
  }
}
let p1 = new Person('Bob')
console.log(p1.name) // print: Bob
// console.log(p1.desc)        // 报错提示:属性“desc”在类型“Person”上不存在
console.log(Person.desc) // print: 人类

五、抽象类: (关键词 abstract)

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。

js
abstract class Department {
    constructor(public name: string) {}
    printName(): void {
        console.log('Department name: ' + this.name);
    }
    abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {
    constructor() {
        super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
    }
    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }
    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}

let department: Department;         // 允许创建一个对抽象类型的引用
// department = new Department();      // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
// department.generateReports();   // 错误: 方法在声明的抽象类中不存在。
// 将上面21行代码调整为 let department: AccountingDepartment; 就不会报错了。

六、接口

我们可以将 类 当作 接口 来使用。

js
class Point {
  x: number
  y: number
}
class Point3d extends Point {
  z: number
}
let point3d: Point3d = {
  x: 1,
  y: 2,
  z: 3
}

参考地址: class 类(中文版)