本文會介紹ES6規范中 instanceof 操作符的實現,以及自定義 instanceof 操作符行為的幾個方法。
文中涉及的規范相關的代碼皆為偽代碼,為了便于理解,其中可能會省略一些參數判斷邏輯或者使用ES語法來代替規范內置的方法,如果發現紕漏,歡迎隨時指出。
instanceof 操作符的實現
InstanceofOperator(O, C)
O instanceof C 會被編譯為方法調用 -- InstanceofOperator(O, C),其實現如下:
InstanceofOperator(O, C){
if(typeof C !== 'object'){
throw TypeError;
}
let instOfHandler = C[Symbol.hasInstance];
if(typeof instOfHandler !== 'undefined'){
return !!instOfHandler.call(C, O);
}
if(typeof C !== 'function'){
throw TypeError;
}
return OrdinaryHasInstance(C, O);
}
該方法首先判斷了 C[Symbol.hasInstance] 方法是否存在,如果存在,就調用;如果不存在,就調用 OrdinaryHasInstance(C, O) 方法。
Function.prototype[Symbol.hasInstance](V)
對于 ES 內置構造器如 Function(), Array() ,其本身是沒有 [Symbol.hasInstance] 屬性的,都繼承自 Function.prototype,這個方法是預定義的,不可修改:
Reflect.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance)
=>
Object {writable: false, enumerable: false, configurable: false, value: function}
其實現如下:
Function.prototype[Symbol.hasInstance](V){
let F = this;
return OrdinaryHasInstance(F, V);
}
比較簡單明了,直接調用 OrdinaryHasInstance(F, V) 方法。
OrdinaryHasInstance(C, O)
上述兩個方法都最終調用到了 OrdinaryHasInstance(C, O) ,其實現如下:
OrdinaryHasInstance(C, O){
if(typeof C !== 'function'){
return false;
}
if(typeof O !== 'object'){
return false;
}
let P = C.prototype;
while(true){
let O = Object.getPrototypeOf(O);
if(O === null){
return false;
}
if(SameValue(P, O)){
return true;
}
}
}
這個方法是判斷 C.prototype 是否在 O 的原型鏈上。
知道了 instanceof 操作符的實現原理,可以發現有3個地方可以自定義操作符行為。
自定義 instanceof 操作符行為的幾個方法
- InstanceofOperator(O, C) 方法中的 let instOfHandler = C[Symbol.hasInstance]
這是對操作符右側變量做修改
普通的對象,默認是沒有 [Symbol.hasInstance] 屬性的,也繼承不到內置的 Function.prototype[Symbol.hasInstance]() 方法:
let o = {};
let a = new Array();
console.log(a instanceof Array) // true
console.log(a instanceof o) // Uncaught TypeError: Right-hand side of 'instanceof' is not callable
如果要避免報錯,可以讓 o 繼承系統內置方法:
Reflect.setPrototypeOf(o, Function.prototype);
console.log(a instanceof o) // false
也可以直接給其添加 [Symbol.hasInstance] 屬性:
Reflect.defineProperty(o, Symbol.hasInstance, {
value(instance){
return Array.isArray(instance);
}
});
console.log(a instanceof o) // true
一種更常規的自定義方法是:
class C {
static [Symbol.hasInstance](instance){
return false;
}
}
let c = new C();
console.log(c instanceof C) // false
注意,這里定義的是靜態方法,是直接掛在 C 上的方法,而不是實例方法:
Reflect.getOwnPropertyDescriptor(C, Symbol.hasInstance);
=>
Object {writable: true, enumerable: false, configurable: true, value: function}
使用傳統的模擬構造函數法:
function F(){}
Reflect.defineProperty(F, Symbol.hasInstance, {
value(instance){
return false;
}
});
let f = new F();
console.log(f instanceof F) // false
內置構造器也是可以添加 Symbol.hasInstance 方法的:
Reflect.defineProperty(Array, Symbol.hasInstance, {
value(instance){ return typeof instance === 'function';}
})
console.log(Array[Symbol.hasInstance]) // function value(instance){ return typeof instance === 'function';}
console.log([] instanceof Array) // false
console.log(function(){} instanceof Array) // true
注意,如果不使用 defineProperty 方法,而是用 [] 的方法來設置屬性的話,是不生效的:
Array[Symbol.hasInstance] = function(){ return typeof instance === 'function';}
console.log(Array[Symbol.hasInstance]) // function [Symbol.hasInstance]() { [native code] }
- OrdinaryHasInstance(C, O) 方法中的 let P = C.prototype;
也是對操作符右側變量做修改
function F(){}
F.prototype = {};
let f = new F();
console.log(f instanceof F) // true
F.prototype = {};
console.log(f instanceof F) // false
在實例化之后,再重新設置構造函數的 prototype 屬性,會導致修改之前創建的實例做 instanceof 操作時不再符合預期。
- OrdinaryHasInstance(C, O) 方法中的 let O = Object.getPrototypeOf(O)
這是對操作符左側變量做修改:
var a = new Array();
console.log(a instanceof Array) // true
Object.setPrototypeOf(a, Function.prototype);
console.log(a instanceof Array) // false
console.log(a instanceof Function) // true
對 a 的原型鏈上的任何環節做修改,都可以改變 instanceof 操作符的行為。
以上是從純語法的方面來考慮 instanceof 操作符的行為,當涉及到瀏覽器環境中時,還會有一些需要特別注意的地方。
跨 frame 或 window 的情況
同一個頁面中不同的 frame 之間,以及主頁面與 frame 之間,有著不同的上下文執行環境,和不同的內置對象。當 instanceof 操作符涉及到多個 frame 時,就會出現一些非預期的情況:
[] instanceof window.frames[0].Array // false
因為 [] 是由主頁面中的 Array 生成的,跟 frame 中的 Array 并無關聯。當頁面中有多個 frame 之間的數據交換時,要特別注意這一點。
文章列表