文章出處

本文會介紹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 之間的數據交換時,要特別注意這一點。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()