處理 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 轉換和轉換成 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 進一步閱讀