本文 AI 產出,尚未審核

TypeScript – 型別推論與型別保護:instanceof 型別保護


簡介

在 TypeScript 中,型別安全是提升程式可讀性與維護性的關鍵。型別保護(type guard) 讓編譯器在執行期動態判斷後,能正確推斷變數的具體型別,從而避免許多潛在的執行錯誤。
instanceof 是最直觀且常用的型別保護之一,它利用 JavaScript 的原型鏈機制,檢查物件是否為某個建構子(class)的實例。對於 類別(class)內建物件(如 DateRegExp)的型別判斷,instanceof 不僅簡潔,而且在編譯期能協助 TypeScript 正確推斷型別。

本篇文章將深入說明 instanceof 的運作原理、使用方式、常見陷阱與最佳實踐,並提供多個實務範例,幫助從初學者到中級開發者在日常開發中正確應用這項型別保護。


核心概念

1. instanceof 的語法與行為

obj instanceof Constructor
  • obj:任意值(通常是物件)。
  • Constructor:一個具有 prototype 屬性的函式或類別。
  • 回傳值:若 obj 的原型鏈上存在 Constructor.prototype,則回傳 true,否則 false

在 TypeScript 中,若條件為 true,編譯器會把 obj 的型別 窄化(narrow)Constructor 的實例型別。


2. 基本範例:類別與 instanceof

class Animal {
  move() {
    console.log('移動中...');
  }
}

class Dog extends Animal {
  bark() {
    console.log('汪汪!');
  }
}

function makeSound(a: Animal) {
  if (a instanceof Dog) {
    // a 被窄化為 Dog
    a.bark();          // ✅ 正確
  } else {
    // a 仍為一般的 Animal
    a.move();          // ✅ 正確
  }
}

重點:在 if (a instanceof Dog) 成立的分支裡,a 的型別已從 Animal 推斷為 Dog,因此可以安全呼叫 bark()


3. 內建物件的型別保護

function format(value: unknown) {
  if (value instanceof Date) {
    // value 被窄化為 Date
    return value.toISOString();   // ✅
  }

  if (value instanceof RegExp) {
    // value 被窄化為 RegExp
    return value.source;          // ✅
  }

  return String(value);
}

DateRegExp 等內建建構子同樣支援 instanceof,讓我們在處理 unknownany 時能安全轉型。


4. 多層繼承與 instanceof

class Vehicle {}
class Car extends Vehicle {}
class SportsCar extends Car {}

function describe(v: Vehicle) {
  if (v instanceof SportsCar) {
    console.log('這是一台跑車');
  } else if (v instanceof Car) {
    console.log('這是一台一般的汽車');
  } else {
    console.log('這是一種其他交通工具');
  }
}

即使 v 的實際型別是 SportsCarinstanceof Car 仍會回傳 true,因為 SportsCar 的原型鏈上包含 Car.prototype。這點在設計分層判斷時非常重要。


5. 與自訂型別保護結合

instanceof 可以與 自訂型別保護函式 結合,提供更彈性的判斷邏輯。

class Cat {
  meow() {
    console.log('喵喵!');
  }
}

// 自訂型別保護
function isCat(obj: any): obj is Cat {
  return obj instanceof Cat;
}

function interact(animal: unknown) {
  if (isCat(animal)) {
    animal.meow();   // ✅ 型別已被窄化為 Cat
  } else if (animal instanceof Dog) {
    animal.bark();   // ✅ 型別已被窄化為 Dog
  }
}

使用 obj is Cat 的語法,讓 TypeScript 能在自訂函式內部告訴編譯器「此條件成立時,objCat」的資訊。


常見陷阱與最佳實踐

陷阱 說明 解決方式
1. 直接對原始值使用 instanceof nullundefined、原始類型(number、string)沒有原型鏈,會拋出錯誤。 先檢查 obj != null,或使用 typeof 先過濾。
2. 判斷跨框架(iframe、worker)物件 每個執行環境都有自己的全域建構子,instanceof 只在同一執行環境有效。 使用 Object.prototype.toString.call(obj)Array.isArray 等跨環境檢查。
3. 把介面(interface)當作建構子 介面在編譯後不存在實體,instanceof 無法使用。 只能對實際的 class、function、內建建構子使用。
4. 只檢查子類別,忽略父類別 若只寫 if (obj instanceof SubClass),可能漏掉 BaseClass 的情況。 按需求排列判斷順序,或同時檢查父類別。
5. 使用 instanceof 判斷字面量物件 {} 並非任何建構子的實例,永遠回傳 false 使用結構型別(duck typing)或自訂型別保護。

最佳實踐

  1. 先做 null / undefined 防護

    if (obj && obj instanceof MyClass) { … }
    
  2. instanceof 判斷抽成函式,提升可讀性與重用性。

    const isDate = (v: unknown): v is Date => v instanceof Date;
    
  3. 配合 unknown / any 使用,在接收外部資料時先以 instanceof 確認型別,再進行後續操作。

  4. 注意執行環境,若程式會在不同 iframe、Web Worker 或 Node VM 中執行,盡量避免直接使用 instanceof,改用更通用的檢查方式。


實際應用場景

1. API 回傳資料的型別辨識

async function fetchData(url: string): Promise<unknown> {
  const resp = await fetch(url);
  return resp.json();   // 回傳 unknown
}

async function handle(url: string) {
  const data = await fetchData(url);

  if (data instanceof Date) {
    console.log('收到日期:', data.toLocaleString());
  } else if (Array.isArray(data)) {
    console.log('收到陣列,長度:', data.length);
  } else if (typeof data === 'object' && data !== null) {
    console.log('收到一般物件');
  } else {
    console.log('其他型別');
  }
}

在處理不確定型別的 JSON 回傳時,instanceof 能快速分辨 DateArray(配合 Array.isArray)等常見型別。

2. UI 元件庫的多型渲染

abstract class Shape {
  abstract draw(): void;
}
class Circle extends Shape {
  draw() { console.log('畫圓形'); }
}
class Rectangle extends Shape {
  draw() { console.log('畫矩形'); }
}

function render(shape: Shape) {
  if (shape instanceof Circle) {
    // 特定的 Circle 渲染邏輯
    shape.draw();
  } else if (shape instanceof Rectangle) {
    // 特定的 Rectangle 渲染邏輯
    shape.draw();
  }
}

在圖形編輯器或遊戲開發中,instanceof 讓我們在同一介面(Shape)下根據具體子類別執行不同的繪製流程。

3. 服務端的錯誤類別分流

class AppError extends Error {}
class NotFoundError extends AppError {}
class ValidationError extends AppError {}

function handleError(err: unknown) {
  if (err instanceof NotFoundError) {
    console.warn('資源未找到');
  } else if (err instanceof ValidationError) {
    console.warn('資料驗證失敗');
  } else if (err instanceof AppError) {
    console.error('一般應用錯誤');
  } else {
    console.error('未知錯誤', err);
  }
}

透過 instanceof,我們能在錯誤處理器中針對不同錯誤類別給予客製化回應。


總結

  • instanceof型別保護 中最直觀且效能友好的工具,適用於 類別內建建構子 以及 跨層繼承 的型別判斷。
  • 正確使用 instanceof 能讓 TypeScript 在 執行期 動態檢查後,編譯期 正確窄化型別,減少 any/unknown 帶來的風險。
  • 必須留意 null/undefined跨執行環境、以及 介面無實體 等常見陷阱,並以 防護式寫法抽離檢查函式 等最佳實踐提升程式碼品質。
  • API 回傳處理UI 多型渲染錯誤分流 等實務情境中,instanceof 能提供乾淨、可讀且安全的型別分支。

掌握 instanceof 的使用技巧,將讓你的 TypeScript 程式碼在型別安全與可維護性上更上一層樓。祝你寫程式快樂,型別無慮!