2021 年 12 月 12 日

類別檢查:「instanceof」

instanceof 算子允許檢查物件是否屬於某個類別。它也會考慮繼承。

在許多情況下,此類檢查可能是必要的。例如,它可用於建立多型函式,該函式會根據引數的類型以不同的方式處理引數。

instanceof 算子

語法為

obj instanceof Class

如果 obj 屬於 Class 或繼承自 Class 的類別,則它會傳回 true

例如

class Rabbit {}
let rabbit = new Rabbit();

// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // true

它也可以搭配建構函式使用

// instead of class
function Rabbit() {}

alert( new Rabbit() instanceof Rabbit ); // true

…以及內建類別,例如 Array

let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true

請注意,arr 也屬於 Object 類別。這是因為 Array 原型上繼承自 Object

通常,instanceof 會檢查原型鏈以進行檢查。我們也可以在靜態方法 Symbol.hasInstance 中設定自訂邏輯。

obj instanceof Class 的演算法大致如下

  1. 如果有一個靜態方法 Symbol.hasInstance,則只需呼叫它:Class[Symbol.hasInstance](obj)。它應該傳回 truefalse,然後我們就完成了。這就是我們可以自訂 instanceof 行為的方式。

    例如

    // setup instanceOf check that assumes that
    // anything with canEat property is an animal
    class Animal {
      static [Symbol.hasInstance](obj) {
        if (obj.canEat) return true;
      }
    }
    
    let obj = { canEat: true };
    
    alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called
  2. 大多數類別沒有 Symbol.hasInstance。在這種情況下,會使用標準邏輯:obj instanceOf Class 會檢查 Class.prototype 是否等於 obj 原型鏈中的某個原型。

    換句話說,逐一比較

    obj.__proto__ === Class.prototype?
    obj.__proto__.__proto__ === Class.prototype?
    obj.__proto__.__proto__.__proto__ === Class.prototype?
    ...
    // if any answer is true, return true
    // otherwise, if we reached the end of the chain, return false

    在上面的範例中,rabbit.__proto__ === Rabbit.prototype,因此會立即給出答案。

    在繼承的情況下,會在第二個步驟進行比對

    class Animal {}
    class Rabbit extends Animal {}
    
    let rabbit = new Rabbit();
    alert(rabbit instanceof Animal); // true
    
    // rabbit.__proto__ === Animal.prototype (no match)
    // rabbit.__proto__.__proto__ === Animal.prototype (match!)

以下是 rabbit instanceof AnimalAnimal.prototype 比較的說明

順帶一提,還有一個方法 objA.isPrototypeOf(objB),如果 objA 出現在 objB 的原型鏈中,則傳回 true。因此,obj instanceof Class 的測試可以改寫為 Class.prototype.isPrototypeOf(obj)

有趣的是,Class 建構函式本身不會參與檢查!只有原型鏈和 Class.prototype 才重要。

當物件建立後,如果 prototype 屬性發生變更,可能會導致有趣的結果。

就像這裡

function Rabbit() {}
let rabbit = new Rabbit();

// changed the prototype
Rabbit.prototype = {};

// ...not a rabbit any more!
alert( rabbit instanceof Rabbit ); // false

加碼:Object.prototype.toString 用於類型

我們已經知道純粹物件會轉換成字串,表示為 [object Object]

let obj = {};

alert(obj); // [object Object]
alert(obj.toString()); // the same

那是它們實作 toString 的方式。但有一個隱藏功能,讓 toString 實際上比這強大得多。我們可以用它作為延伸的 typeofinstanceof 的替代方案。

聽起來很奇怪?的確。讓我們來破解迷思。

根據 規範,內建的 toString 可以從物件中取出,並在任何其他值的內容中執行。而其結果取決於該值。

  • 對於數字,它會是 [object Number]
  • 對於布林值,它會是 [object Boolean]
  • 對於 null[object Null]
  • 對於 undefined[object Undefined]
  • 對於陣列:[object Array]
  • …等(可自訂)。

讓我們示範一下

// copy toString method into a variable for convenience
let objectToString = Object.prototype.toString;

// what type is this?
let arr = [];

alert( objectToString.call(arr) ); // [object Array]

在這裡,我們使用 call,如 裝飾器和轉發,call/apply 章節中所述,在內容 this=arr 中執行函式 objectToString

在內部,toString 演算法會檢查 this 並傳回對應的結果。更多範例

let s = Object.prototype.toString;

alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]

Symbol.toStringTag

物件 toString 的行為可以使用特殊物件屬性 Symbol.toStringTag 自訂。

例如

let user = {
  [Symbol.toStringTag]: "User"
};

alert( {}.toString.call(user) ); // [object User]

對於大多數環境特定的物件,都有這樣的屬性。以下是一些瀏覽器特定的範例

// toStringTag for the environment-specific object and class:
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest

alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]

如你所見,結果正是 Symbol.toStringTag(如果存在),包覆在 [object ...] 中。

最後,我們有「類固醇上的 typeof」,它不僅適用於基本資料類型,也適用於內建物件,甚至可以自訂。

當我們想要取得類型為字串,而不是僅檢查時,我們可以使用 {}.toString.call 取代內建物件的 instanceof

摘要

讓我們總結一下我們所知道的類型檢查方法

適用於 傳回
typeof 基本類型 字串
{}.toString 基本類型、內建物件、具有 Symbol.toStringTag 的物件 字串
instanceof 物件 真/假

如我們所見,{}.toString 在技術上是一個「更進階」的 typeof

當我們使用類別階層並想要檢查類別時,考量到繼承,instanceof 算子會真正發揮作用。

作業

重要性:5

在以下程式碼中,為什麼 instanceof 會傳回 true?我們可以輕易看出 a 不是由 B() 建立的。

function A() {}
function B() {}

A.prototype = B.prototype = {};

let a = new A();

alert( a instanceof B ); // true

是的,看起來確實很奇怪。

但是 instanceof 並不關心函式,而是關心它的 prototype,它會與原型鏈進行比對。

而這裡 a.__proto__ == B.prototype,因此 instanceof 傳回 true

所以,根據 instanceof 的邏輯,prototype 實際上定義了類型,而不是建構函式。

教學課程地圖

留言

留言前請先閱讀…
  • 如果你有建議要如何改進 - 請 提交 GitHub 問題 或發起拉取請求,而不是留言。
  • 如果你無法理解文章中的某些內容 – 請詳細說明。
  • 若要插入少數幾個字元的程式碼,請使用 <code> 標籤,若要插入多行程式碼,請將它們包覆在 <pre> 標籤中,若要插入超過 10 行程式碼,請使用沙盒 (plnkrjsbincodepen…)