深入理解 TypeScript
请支持本书:购买捐赠
(广告,请勿屏蔽。)


在本章中,我们将研究与类及其实例相关的 TypeScript 类型。

17.1 类的两条原型链

考虑以下类

class Counter extends Object {
  static createZero() {
    return new Counter(0);
  }
  value: number;
  constructor(value: number) {
    super();
    this.value = value;
  }
  increment() {
    this.value++;
  }
}
// Static method
const myCounter = Counter.createZero();
assert.ok(myCounter instanceof Counter);
assert.equal(myCounter.value, 0);

// Instance method
myCounter.increment();
assert.equal(myCounter.value, 1);
Figure 2: Objects created by class Counter. Left-hand side: the class and its superclass Object. Right-hand side: The instance myCounter, the prototype properties of Counter, and the prototype methods of the superclass Object..

图 2 中的图表显示了类 Counter 的运行时结构。该图中有两条对象原型链

在本章中,我们将首先探讨实例对象,然后探讨作为对象的类。

17.2 类实例的接口

接口指定对象提供的服务。例如

interface CountingService {
  value: number;
  increment(): void;
}

TypeScript 的接口按 结构 工作:为了使对象实现接口,它只需要具有正确类型和正确名称的属性。我们可以在以下示例中看到这一点

const myCounter2: CountingService = new Counter(3);

结构化接口很方便,因为即使对于已经存在的对象,我们也可以为其创建接口(即,我们可以在事后引入它们)。

如果我们事先知道某个对象必须实现某个给定的接口,那么尽早检查它是否实现了该接口通常是有意义的,以避免以后出现意外。我们可以通过 implements 对类实例执行此操作

class Counter implements CountingService {
  // ···
};

注释

17.3 类的接口

类本身也是对象(函数)。因此,我们可以使用接口来指定它们的属性。这里的主要用例是描述对象的工厂。下一节将给出一个示例。

17.3.1 示例:从 JSON 转换和转换为 JSON

以下两个接口可用于支持其实例与 JSON 相互转换的类

// Converting JSON to instances
interface JsonStatic {
  fromJson(json: any): JsonInstance;
}

// Converting instances to JSON
interface JsonInstance {
  toJson(): any;
}

我们在以下代码中使用这些接口

class Person implements JsonInstance {
  static fromJson(json: any): Person {
    if (typeof json !== 'string') {
      throw new TypeError(json);
    }
    return new Person(json);
  }
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  toJson(): any {
    return this.name;
  }
}

这就是我们可以立即检查类 Person(作为对象)是否实现了接口 JsonStatic 的方法

// Assign the class to a type-annotated variable
const personImplementsJsonStatic: JsonStatic = Person;

以下进行此检查的方法似乎是个好主意

const Person: JsonStatic = class implements JsonInstance {
  // ···
};

但是,这实际上行不通

17.3.2 示例:TypeScript 为类 Object 及其实例提供的内置接口

查看 TypeScript 的内置类型是有益的

一方面,接口 ObjectConstructor 用于类 Object 本身

/**
 * Provides functionality common to all JavaScript objects.
 */
declare var Object: ObjectConstructor;

interface ObjectConstructor {
  new(value?: any): Object;
  (): any;
  (value: any): any;

  /** A reference to the prototype for a class of objects. */
  readonly prototype: Object;

  /**
   * Returns the prototype of an object.
   * @param o The object that references the prototype.
   */
  getPrototypeOf(o: any): any;

}

另一方面,接口 Object 用于 Object 的实例

interface Object {
  /** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
  constructor: Function;

  /** Returns a string representation of an object. */
  toString(): string;
}

名称 Object 在两个不同的 语言级别 使用了两次

17.4 类作为类型

考虑以下类

class Color {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

此类定义创建了两个东西。

首先,一个名为 Color 的构造函数(可以通过 new 调用)

assert.equal(
  typeof Color, 'function')

其次,一个名为 Color 的接口,它与 Color 的实例相匹配

const green: Color = new Color('green');

以下是 Color 确实是一个接口的证明

interface RgbColor extends Color {
  rgbValue: [number, number, number];
}

17.4.1 陷阱:类按结构工作,而不是按名称工作

不过,有一个陷阱:使用 Color 作为静态类型并不是一个非常严格的检查

class Color {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const person: Person = new Person('Jane');
const color: Color = person; // (A)

为什么 TypeScript 在 A 行没有报错?这是由于结构类型化:PersonColor 的实例具有相同的结构,因此在静态上是兼容的。

17.4.1.1 关闭结构类型化

我们可以通过添加私有属性来使这两组对象不兼容

class Color {
  name: string;
  private branded = true;
  constructor(name: string) {
    this.name = name;
  }
}
class Person {
  name: string;
  private branded = true;
  constructor(name: string) {
    this.name = name;
  }
}

const person: Person = new Person('Jane');

// @ts-expect-error: Type 'Person' is not assignable to type 'Color'.
//   Types have separate declarations of a private property
//   'branded'. (2322)
const color: Color = person;

在这种情况下,私有属性会关闭结构类型化。

17.5 延伸阅读