# 八、类(Class)

TypeScript 完全支持 ES2015 引入的 class 关键字。

# 1.基本用法

class Point {
    x: number; // 属性“x”没有初始化表达式,且未在构造函数中明确赋值。
    o!: string; // 你可以使用明确赋值断言操作符,来消减上面的错误(definite assignment assertion operator) !:
    y; // 成员“y”隐式包含类型“any”。
    z = 1; // Point.z: number 初始值的设置,会让Ts自动推断出它的类型
}

const point = new Point();
point.z = '1'; // 不能将类型“string”分配给类型“number”。

# 2.类属性

readonly 字段可以添加一个 readonly 前缀修饰符,这会阻止在构造函数之外的赋值。

class Point {
    readonly r: number
    constructor(){
        this.r = 100;
    }
    setR(){
        this.r = 200; // 无法分配到 "r" ,因为它是只读属性。
    }
}

const point = new Point();
point.r = 200; // 无法分配到 "r" ,因为它是只读属性。ts

Constructors 方法是类的默认构造函数方法,一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。 构造函数返回类实例类型

class Point {
    x: number;
    y: number;

    // Normal signature with defaults
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}

取值函数(getter)和存值函数(setter)

在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class Point {
    get prop() {
        return "getter";
    }
    set prop(value) {
        console.log("setter: " + value);
    }
}

const inst = new Point();
inst.prop = '123'; // setter: 123
console.log(inst.prop); // getter

TypeScript 对存取器有一些特殊的推断规则:

  • 如果 get 存在而 set 不存在,属性会被自动设置为 readonly
  • 如果 setter 参数的类型没有指定,它会被推断为 getter 的返回类型

# 3.类的继承

Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承,要清晰和方便很多。

class Point {
}

class ColorPoint extends Point {
}

ColorPoint 作为子类继承了 Point 的所有属性方法,当然目前两个类都是空的

# 3.1 super

super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

class Foo {
    constructor() {
        console.log(1);
    }
}

class Bar extends Foo {
    constructor() {
        super();
        console.log(2);
    }
}

const bar = new Bar();
// 1
// 2

为什么子类的构造函数,一定要调用super()?原因就在于:ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。这就是为什么 ES6 的继承必须先调用super()方法,因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。

可见此时的执行顺序是:

  1. 基类字段初始化
  2. 基类构造函数运行
  3. 派生类字段初始化
  4. 派生类构造函数运行

第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类

class Base {
    greet() {
        console.log("Hello, world!");
    }
}

class Derived extends Base {
    greet(name?: string) {
        if (name === undefined) {
            // super: class Base
            super.greet();
        } else {
            console.log(`Hello, ${name.toUpperCase()}`);
        }
    }
}

const d = new Derived();
d.greet();
d.greet("reader");

# 3.2 implements

你可以使用 implements 语句检查一个类是否满足一个特定的 interface。如果一个类没有正确的实现(implement)它,TypeScript 会报错:

interface classT {
    func1(): string;
}

class example1 implements classT {
    func1() {
        return "str";
    }
}
class example2 implements classT {
    // 类型 "example2" 中缺少属性 "func1",但类型 "classT" 中需要该属性。
    func2() {
        return 1;
    }
}

# 4.成员可见性

你可以使用 TypeScript 控制某个方法或者属性是否对类以外的代码可见。

public(默认) 可以在任何地方被获取,因为 public 是默认的可见性修饰符,所以你不需要写它,除非处于格式或者可读性的原因。

class Greeter {
    public greet() {
        console.log("hi!");
    }
}
const g = new Greeter();
g.greet();

protected 成员仅仅对子类可见:

class Base {
    protected hi() {
        console.log("hi!");
    }
    protected name = 1;
}
const inst = new Base();
console.log(inst.name); // 属性“name”受保护,只能在类“Base”及其子类中访问。

class personal extends Base {
    say() {
        this.hi();
    }
}

private 不允许访问的成员,即便是子类。

class Base {
    private x = 0;
}
class Derived extends Base {
    // 类“Derived”错误扩展基类“Base”。
    // 属性“x”在类型“Base”中是私有属性,但在类型“Derived”中不是。t
    x = 1;
}

"#" 强私有

class Base {
    #w = 100
    #r = 100
}

const base = new Base();
base.#w; // 属性 "#w" 在类 "Base" 外部不可访问,因为它具有专用标识符。

# 5.泛型类(Generic Classes)

类跟接口一样,也可以写泛型。当使用 new 实例化一个泛型类,它的类型参数的推断跟函数调用是同样的方式:

class Base<T> {
    content: T;
    constructor(content: T) {
        this.content = content;
    }
}

const boy = new Base("string");
boy.content; // Base<string>.content: string

# 6.抽象类和成员

TypeScript 中,类、方法、字段都可以是抽象的(abstract)。就好比“模子”

抽象方法或者抽象字段是不提供实现的。这些成员必须存在在一个抽象类中,这个抽象类也不能直接被实例化。

让我们看个例子:

abstract class Base {
    abstract getName(): string;

    printName() {
        console.log("Hello, " + this.getName());
    }
}

const b = new Base(); // 无法创建抽象类的实例。

我们不能使用 new 实例 Base 因为它是抽象类。我们需要写一个派生类,并且实现抽象成员。

class Derived extends Base {
    getName() {
        return "world";
    }
}

const d = new Derived();
d.printName();

注意,如果我们忘记实现基类的抽象成员,我们会得到一个报错:

class Derived extends Base {
    // 非抽象类“Derived”不会实现继承自“Base”类的抽象成员“getName”。
    getAge() {
        return "world";
    }
}
Last Updated: 12/22/2022, 10:36:15 PM