TypeScript – 型別推論與型別保護:instanceof 型別保護
簡介
在 TypeScript 中,型別安全是提升程式可讀性與維護性的關鍵。型別保護(type guard) 讓編譯器在執行期動態判斷後,能正確推斷變數的具體型別,從而避免許多潛在的執行錯誤。instanceof 是最直觀且常用的型別保護之一,它利用 JavaScript 的原型鏈機制,檢查物件是否為某個建構子(class)的實例。對於 類別(class) 與 內建物件(如 Date、RegExp)的型別判斷,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);
}
Date、RegExp 等內建建構子同樣支援 instanceof,讓我們在處理 unknown 或 any 時能安全轉型。
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 的實際型別是 SportsCar,instanceof 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 能在自訂函式內部告訴編譯器「此條件成立時,obj 為 Cat」的資訊。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
1. 直接對原始值使用 instanceof |
null、undefined、原始類型(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)或自訂型別保護。 |
最佳實踐
先做
null/undefined防護if (obj && obj instanceof MyClass) { … }將
instanceof判斷抽成函式,提升可讀性與重用性。const isDate = (v: unknown): v is Date => v instanceof Date;配合
unknown/any使用,在接收外部資料時先以instanceof確認型別,再進行後續操作。注意執行環境,若程式會在不同 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 能快速分辨 Date、Array(配合 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 程式碼在型別安全與可維護性上更上一層樓。祝你寫程式快樂,型別無慮!