[ts]class

38 阅读3分钟

官网链接

类成员

class Point {
    x: number;
    y: number;
}

const pt = new Point();
pt.x = 0;
pt.y = 0;

字段类型说明可选,如果没指定,则为any类型。

class Point {
    x = 0;
    y = 0;
}

也可以自动推导出类型。

--strictPropertyInitialization

class GoodGreeter {
    name: string;
    
    constructor() {
        this.name = "hello";
    }
}

class OKGreeter {
    // 使用断言,不在构造函数中初始化也可以
    // Not initialized, but no error
    name!: string;
}

readonly

class Greeter {
    readonly name: string = "world";

    constructor(otherName?: string) {
        if (otherName !== undefined) {
            this.name = otherName;
        }
    }
}

readonly防止在构造函数外,重新赋值

构造函数

class Point {
    x: number;
    y: number;
 
    // Normal signature with defaults
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}
class Point {
    // Overloads
    constructor(x: number, y: string);
    constructor(s: string);
    constructor(xs: any, y?: any) {
        // TBD
    }
}

Super调用

子类构造函数在使用this前,必须先调用super()

方法

class Point {
    x = 10;
    y = 10;

    scale(n: number): void {
        this.x *= n;
        this.y *= n;
    }
}

Getters / Setters

class C {
    _length = 0;

    get length() {
        return this._length;
    }

    set length(value) {
        this._length = value;
    }
}
  • 如果只有get,那属性为readonly
  • 如果setter的参数类型没指定,则从getter返回类型推导
  • getters和setters成员可见性必须一致

索引签名

class MyClass {
    [s: string]: boolean | ((s: string) => boolean);

    check(s: string) {
        return this[s] as boolean;
    }
}

类继承

implements

可以使用implements来检查一个类是否满足特定的接口

interface Pingable {
    ping(): void;
}

class Sonar implements Pingable {
    ping() {
        console.log("ping!");
    }
}

 

class Ball implements Pingable {
    // error
    pong() {
        console.log("pong!");
    }
}

类可以实现多个接口class C implements A, B {

extends

类可以从一个基类继承,派生类有基类的所有属性和方法,也可以定义额外的成员。

class Animal {
    move() {
        console.log("Moving along!");
    }
}

 

class Dog extends Animal {
    woof(times: number) {
        for (let i = 0; i < times; i++) {
            console.log("woof!");
        }
    }
}

const d = new Dog();
// Base class method

d.move();
// Derived class method
d.woof(3);

覆盖方法

一个派生类可以覆盖基类的字段或属性。可以使用super.语法来访问父类的方法。

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

 

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

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

初始化顺序

class Base {
    name = "base";
    constructor() {
        console.log("My name is " + this.name);
    }
}

class Derived extends Base {
    name = "derived";
}

// Prints "base", not "derived"
const d = new Derived();

顺序

  • 基类字段初始化
  • 基类构造函数执行
  • 派生类字段初始化
  • 派生类构造函数执行

继承内置类型

class MsgError extends Error {
    constructor(m: string) {
        super(m);
        // Set the prototype explicitly.
        Object.setPrototypeOf(this, MsgError.prototype);
    }

    sayHello() {
        return "hello " + this.message;
    }
}

需要手动设置原型指向。

成员可见性

public

类成员的默认可见性为public,一个public成员可以在任何地方被访问。

protected

protected成员只有当前类,子类中可见。

class Greeter {
    public greet() {
        console.log("Hello, " + this.getName());
    }

    protected getName() {
        return "hi";
    }
}

 

class SpecialGreeter extends Greeter {
    public howdy() {
        // OK to access protected member here
        console.log("Howdy, " + this.getName());
    }
}

const g = new SpecialGreeter();

g.greet(); // OK
// `Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.
g.getName();

暴漏protected成员

class Base {
    protected m = 10;
}

class Derived extends Base {
    // No modifier, so default is 'public'
    m = 15;
}

const d = new Derived();
console.log(d.m); // OK

跨层级protected的访问

class Base {
    protected x: number = 1;
}

class Derived1 extends Base {
    protected x: number = 5;
}

class Derived2 extends Base {
    f1(other: Derived2) {
        other.x = 10;
    }

    f2(other: Base) {
        // Property 'x' is protected and only accessible through an instance of class 'Derived2'. This is an instance of class 'Base'.
        other.x = 10;
    }
}

private

只能在当前类中访问

class Base {
    private x = 0;
}

const b = new Base();
// Can't access from outside the class
console.log(b.x);

跨实例private访问

class A {
    private x = 10;
 
    public sameAs(other: A) {
        // No error
        return other.x === this.x;
    }
}

注意事项

protectedprivate只在类型检查中是强制的,编译成js,protectedprivate不会生效。

class Dog {
    // js private
    #barkAmount = 0;
    personality = "happy";

    constructor() {}
}

静态成员

类可以有static成员,这些成员和类的实例无关,只能通过类的构造函数来访问。

class MyClass {
    static x = 0;
    static printX() {
        console.log(MyClass.x);
    }
}

console.log(MyClass.x);
MyClass.printX();

静态成员也可以有publicprotectedprivate修饰。

class MyClass {
    private static x = 0;
}

// Property 'x' is private and only accessible within class 'MyClass'.
console.log(MyClass.x);

静态成员也可以被继承

class Base {
    static getGreeting() {
        return "Hello world";
    }
}

class Derived extends Base {
    myGreeting = Derived.getGreeting();
}

特殊静态名称

覆盖Function原型的属性并不安全。因为类是可以通过new执行的函数,特定的static名称不能使用,比如Functionname, length, call属性。

class S {
    // Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'S'
    static name = "S!";
}

staticBlocks

class Foo {
    static #count = 0;

    get count() {
        return Foo.#count;
    }

 

    static {
        try {
            const lastInstances = loadLastInstances();
            Foo.#count += lastInstances.length;
        }catch {}
    }

}

泛型类

class Box<Type> {
    contents: Type;
    constructor(value: Type) {
        this.contents = value;
    }
}

const b = new Box("hello!");

this

class MyClass {
    name = "MyClass";
    getName() {
        return this.name;
    }
}

const c = new MyClass();

const obj = {
    name: "obj",
    getName: c.getName,
};

// Prints "obj", not "MyClass"
console.log(obj.getName());

this的值是函数如何被调用时确定的,getName通过obj的引用调用的,所以this的值是obj而不是类的实例(c)

箭头函数

class MyClass {
    name = "MyClass";

    getName = () => {
        return this.name;
    };
}

const c = new MyClass();
const g = c.getName;

// Prints "MyClass" instead of crashing
console.log(g());
  • this值能够保证在运行时正确,即使没有ts的检查
  • 这将使用更过的内存,因为每个实例都有这个方法的拷贝
  • 不能在派生类中使用super.getName,因为在原型链中没入口去查询基类的方法

this参数

// TypeScript input with 'this' parameter
function fn(this: SomeType, x: number) {
    /* ... */
}
class MyClass {
    name = "MyClass";

    getName(this: MyClass) {
        return this.name;
    }
}

const c = new MyClass();
// OK
c.getName();
// Error, would crash
const g = c.getName;
console.log(g());

ts会检查函数调用时this上下文是否正确。通过在方法定义时添加this参数来代替使用箭头函数,静态分析方法是否被正确调用。

与箭头函数比较

  • 类的方法可能会被不正确使用
  • 类中函数只分配一次内存,而不是每个实例上都分配一次
  • 基类方法可以通过super调用

this类型

class Box {
    contents: string = "";
    // (method) Box.set(value: string): this
    set(value: string) {
        this.contents = value;
        return this;
    }
}

this 类型守卫

可以在类中的方法返回位置使用this is Type,当和类型收缩结合时,目标对象的类型可以收缩为指定类型

class Box<T> {
    value?: T;

    hasValue(): this is { value: T } {
        return this.value !== undefined;
    }
}

 

const box = new Box();
box.value = "Gameboy";

// (property) Box<unknown>.value?: unknown
box.value;

if (box.hasValue()) {
    // (property) value: unknown
    box.value;
}

参数属性

class Params {

    constructor(
        public readonly x: number,
        protected y: number,
        private z: number
    ) {
        // No body necessary
    }
}

const a = new Params(1, 2, 3);
// (property) Params.x: number
console.log(a.x);

// Property 'z' is private and only accessible within class 'Params'.
console.log(a.z);

类表达式

类表达式和类的声明非常相似,唯一不同是类表达式不需要name

const someClass = class<Type> {
    content: Type;
    constructor(value: Type) {
        this.content = value;
    }
};

抽象类和成员

ts中类、方法和字段可以是抽象的(abstract)。

一个抽象的方法或者字段没有实现,而且这些成员必须存在于抽象类中,所以抽象类不能被直接实例化。

抽象类的角色就是提供一个基类,让子类实现所有抽象成员。

abstract class Base {
    abstract getName(): string;

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

 
// Cannot create an instance of an abstract class.
const b = new Base();

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

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

抽象构造函数签名

function greet(ctor: new () => Base) {
    const instance = new ctor();
    instance.printName();
}

greet(Derived);

类的关系

class Point1 {
    x = 0;
    y = 0;
}

class Point2 {
    x = 0;
    y = 0;
}


// OK
const p: Point1 = new Point2();
class Empty {}

function fn(x: Empty) {
    // can't do anything with 'x', so I won't
}

// All OK!
fn(window);
fn({});
fn(fn);