Skip to content
Published at:

Classes

TypeScript offers full support for the class keyword introduced in ES2015.

Class Members

ts
// 最基础的class
class Point {}

Fields

成员

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

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

初始化成员,类型推导

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

const point = new Point();
console.log(`x: ${point.x}; y: ${point.y}`);

--strictPropertyInitialization 选项开启后,没有初始化的成员必需在构造函数里初始化

ts
// Error
class BadGreeter {
  name: string; // Property 'name' has no initializer and is not definitely assigned in the constructor.
}

// Ok
class GoodGreeter {
  name: string;

  constructor() {
    this.name = 'hello';
  }
}

// Ok with `!`
class OkGreeter {
  name!: string;
}

readonly

只读属性: 只有在声明和构造函数中去初始化

ts
class Greeter {
  readonly name: string = 'world'; // Ok

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

  err() {
    this.name = 'not ok'; // Error
  }
}

const g = new Greeter();
g.name = 'also not ok'; // Error

Constructors

构造函数和普通函数类似,给参数添加类型声明、默认值、重载

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

  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}

// Overloads
class Point2 {
  constructor(x: number, y: string);
  constructor(s: string);
  constructor(xs: any, y?: any) {
    // TBD
  }
}

Super Calls

super(): 如果有基类,通过this访问成员前,必需调用 super()

ts
class Base {
  k = 4;
}

class Berived extends Base {
  constructor() {
    console.log(this.k); // Error: 'super' must be called before accessing 'this' in the constructor of a derived class.
    super();
  }
}

Methods

方法: Class 里面的函数属性,叫方法; 成员通过 this 来访问

ts
class Point {
  x = 10;
  y = 10;

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

Getters / Setters

accessors 访问器

ts
class C {
  _length = 0;
  get length() {
    return this._length;
  }

  set length(value) {
    this._length = value;
  }
}

accessors 推理规则:

  • 如果一个属性有get没有set,那这个属性会自动变成 readonly
  • 如果一个属性的setter的参数类型没有指定,会去从对应属性的getter的返回值来推导
  • 属性的Getters 和 setters必需有相同的访问权限

TypeScript 4.3之后, Getting和setting 可以有不同的类型

ts
class Thin {
  _size = 0;

  get size(): number {
    return this._size;
  }

  set size(value: string | number | boolean) {
    let num = Number(value);

    if (!Number.isFinite(num)) {
      this._size = 0;
      return;
    }

    this._size = num;
  }
}

Index Signatures

索引签名: class里面可以定义索引签名;和对象类型里面定义索引签名一样(Type alias和interface)

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

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

Class Heritage

implements Clauses

实现interface

ts
interface Pingable {
  ping(): void;
}

class Sonar implements Pingable {
  ping(): void {
    console.log('ping!');
  }
}

class Ball implements Pingable {
  // Error: 没有实现 ping函数
  pong() {
    console.log('pong!');
  }
}

实现多个interface:class C implements A, B {

Cautions

对于实现interface中的可选属性,不会创建这个属性

ts
interface A {
  x: number;
  y?: number;
}

class C implements A {
  x = 0;
}

const c = new C();
c.y = 100; // Error: Property 'y' does not exist on type 'C'.

extends Clauses

ts
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();
d.move(); // Animal class method
d.woof(3); // Gog class method

Overriding Methods

方法重载:用supper.语法访问父类方法

ts
class Base {
  greet() {
    console.log('Hello world!');
  }
}

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

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

// Ok
const b: Base = d;
b.greet();

Type-only Field Declarations

// TODO

Initialization Order

初始化顺序

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

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

const d = new Derived();

执行顺序:

  1. 基类成员初始化
  2. 基类构造函数被调用
  3. 子类成员初始化
  4. 子类构造函数被调用

Inheriting Built-in Types

ts
class MyError extends Error {
  constructor(m: string) {
    super(m);
  }

  sayHello() {
    return 'Hello ' + this.message;
  }
}

Member Visibility

在class外面访问属性和方法的权限

public

默认就是public

ts
class Greeter {
  public greet() {
    console.log('hi!');
  }
}

const g = new Greeter();
g.greet();

protected

只能在子类中访问

ts
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
g.getName(); // Error: Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.

Exposure of protected membersCross-hierarchy protected access

private

protected类似,但在子类中也无法访问

ts
// 外部无法访问
class Base {
  private x = 0;
}
const b = new Base();
console.log(b.x); // Error: Property 'x' is private and only accessible within class 'Base'.

// 子类无法访问
class Derived extends Base {
  showX() {
    console.log(this.x);
    // Error: Property 'x' is private and only accessible within class 'Base'.
  }
}

Cross-instance private access

Caveats

ts
class MySafe {
  private secretKey = 12345;
}

const s = new MySafe();
console.log(s.secretKey); // Error: Property 'secretKey' is private and only accessible within class 'MySafe'.

console.log(s['secretKey']); // OK

Static Member

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

static Blocks in Classes

可以写一些初始化的代码;执行一次

ts
class Foo {
  static #count = 0;
  get count() {
    return Foo.#count;
  }

  // 静态代码块
  static {
    try {
      const lastInstances = loadLastInstances();
      Foo.#count += lastInstances.lenght;
    } catch {}
  }
}

Generic Classes

泛型 Class

ts
class Box<Type> {
  contents: Type;

  constructor(value: Type) {
    this.contents = value;
  }
}

const b = new Box('hello!'); // const b: Box<string>

Type Parameters in Static Members

ts
class Box<Type> {
  static defaultValue: Type; // Error: Static members cannot reference class type parameters
}

this at Runtime in Classes

前提:TS 不会改变 JS 的运行时行为 JS 的 this 包袱; by default, the value of this inside a function depends on how the function was called.

ts
class MyClass {
  name = 'MyClass';
  getName() {
    return this.name;
  }
}
const c = new MyClass();
const obj = {
  name: 'obj',
  getName: c.getName, // 上面的 this 函数会被这里调用,所以 this 指的是 obj,不是 MyClass
};

// 打印的是 "obj", 不是 "MyClass"
console.log(obj.getName());

Arrow Functions

如果如上 this 丢失了上下文,用箭头函数替代方法定义

ts
class MyClass {
  name = 'MyClass';
  getName = () => {
    return this.name;
  };
}

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

// 打印 MyClass
console.log(g());

this parameters

在方法或函数里面定义了 this 参数,TS 在编译时,会把这个参数擦除掉

ts
// 编译前 TS 代码,带 this 参数的函数
function fn(this: SomeType, x: number) {
  /* ... */
}

// 编译成 JS 后
function fn(x) {
  /* ... */
}

this Types

// TODO

this-based type guards

Parameter Properties

把构造函数是的参数前加 权限关键字(public、private、protected、readonly),则会把参数转变成类成员变量 (语法糖:少写代码)

ts
class Params {
  // x/y/z会转变成成员
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {
    // No body necessary
  }
}

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

console.log(a.z); // Error: Property 'z' is private

Class Expressions

类表达式和类声明类似;区别在于类表达式不需要名字;会推导出一个名字

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

const m = new someClass('Hello world'); // const m: someClass<string>

Constructor Signatures

abstract Classes and Members

Abstract Construct Signatures

Relationships Between Classes