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
的演算法大致如下
-
如果有一個靜態方法
Symbol.hasInstance
,則只需呼叫它:Class[Symbol.hasInstance](obj)
。它應該傳回true
或false
,然後我們就完成了。這就是我們可以自訂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
-
大多數類別沒有
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 Animal
與 Animal.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
實際上比這強大得多。我們可以用它作為延伸的 typeof
和 instanceof
的替代方案。
聽起來很奇怪?的確。讓我們來破解迷思。
根據 規範,內建的 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
算子會真正發揮作用。
留言
<code>
標籤,若要插入多行程式碼,請將它們包覆在<pre>
標籤中,若要插入超過 10 行程式碼,請使用沙盒 (plnkr、jsbin、codepen…)