instanceof
操作符用于檢查壹個對象是否屬于某個特定的 class。同時,它還考慮了繼承。
在許多情況下,可能都需要進行此類檢查。例如,它可以被用來構建壹個 多態性(polymorphic) 的函數,該函數根據參數的類型對參數進行不同的處理。
語法:
obj instanceof Class
如果 obj
隸屬于 Class
類(或 Class
類的衍生類),則返回 true
。
例如:
class Rabbit {} let rabbit = new Rabbit(); // rabbit 是 Rabbit class 的對象嗎? alert( rabbit instanceof Rabbit ); // true
它還可以與構造函數壹起使用:
// 這裏是構造函數,而不是 class function Rabbit() {} alert( new Rabbit() instanceof Rabbit ); // true
……與諸如 Array
之類的內建 class 壹起使用:
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
,那就直接調用這個方法:
例如:
// 設置 instanceOf 檢查 // 並假設具有 canEat 屬性的都是 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) 被調用
大多數 class 沒有 Symbol.hasInstance
。在這種情況下,標准的邏輯是:使用 obj instanceOf Class
檢查 Class.prototype
是否等于 obj
的原型鏈中的原型之壹。
換句話說就是,壹個接壹個地比較:
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // 如果任意壹個的答案爲 true,則返回 true // 否則,如果我們已經檢查到了原型鏈的尾端,則返回 false
在上面那個例子中,rabbit.__proto__ === Rabbit.prototype
,所以立即就給出了結果。
而在繼承的例子中,匹配將在第二步進行:
class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); alert(rabbit instanceof Animal); // true // rabbit.__proto__ === Animal.prototype(無匹配) // rabbit.__proto__.__proto__ === Animal.prototype(匹配!)
下圖展示了 rabbit instanceof Animal
的執行過程中,Animal.prototype
是如何參與比較的:
這裏還要提到壹個方法 objA.isPrototypeOf(objB),如果 objA
處在 objB
的原型鏈中,則返回 true
。所以,可以將 obj instanceof Class
檢查改爲 Class.prototype.isPrototypeOf(obj)
。
這很有趣,但是 Class
的 constructor 自身是不參與檢查的!檢查過程只和原型鏈以及 Class.prototype
有關。
創建對象後,如果更改 prototype
屬性,可能會導致有趣的結果。
就像這樣:
function Rabbit() {} let rabbit = new Rabbit(); // 修改了 prototype Rabbit.prototype = {}; // ...再也不是 rabbit 了! alert( rabbit instanceof Rabbit ); // false
大家都知道,壹個普通對象被轉化爲字符串時爲 [object Object]
:
let obj = {}; alert(obj); // [object Object] alert(obj.toString()); // 同上
這是通過 toString
方法實現的。但是這兒有壹個隱藏的功能,該功能可以使 toString
實際上比這更強大。我們可以將其作爲 typeof
的增強版或者 instanceof
的替代方法來使用。
聽起來挺不可思議?那是自然,精彩馬上揭曉。
按照 規範 所講,內建的 toString
方法可以被從對象中提取出來,並在任何其他值的上下文中執行。其結果取決于該值。
對于 number 類型,結果是 [object Number]
對于 boolean 類型,結果是 [object Boolean]
對于 null
:[object Null]
對于 undefined
:[object Undefined]
對于數組:[object Array]
……等(可自定義)
讓我們演示壹下:
// 方便起見,將 toString 方法複制到壹個變量中 let objectToString = Object.prototype.toString; // 它是什麽類型的? let arr = []; alert( objectToString.call(arr) ); // [object Array]
這裏我們用到了在 裝飾器模式和轉發,call/apply 壹章中講過的 call 方法來在上下文 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
方法的行爲。
例如:
let user = { [Symbol.toStringTag]: "User" }; alert( {}.toString.call(user) ); // [object User]
對于大多數特定于環境的對象,都有壹個這樣的屬性。下面是壹些特定于浏覽器的示例:
// 特定于環境的對象和類的 toStringTag: 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 | 原始數據類型 | string |
{}.toString | 原始數據類型,內建對象,包含 Symbol.toStringTag 屬性的對象 | string |
instanceof | 對象 | true/false |
正如我們所看到的,從技術上講,{}.toString
是壹種“更高級的” typeof
。
當我們使用類的層次結構(hierarchy),並想要對該類進行檢查,同時還要考慮繼承時,這種場景下 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
,而不是構造函數。