JS 原生方法原理探究(五):如何實現 instanceof?

這是JS 原生方法原理探究系列的第五篇文章。本文會介紹如何實現 instanceof 方法。 數組

typeof 操做符返回一個表示數據類型的字符串,它能夠應付常規場景下的數據類型判斷。對基本數據類型 undefinedbooleanstringnumberSymbol 和引用數據類型 function 均可以正確判斷,可是對 null、數組、對象等則統一返回 "object"。ide

好比說:函數

function F1(){}
function F2(){}
const obj1 = new F1()
const obj2 = new F2()
typeof obj1            // ‘object’
typeof obj2           // 'object'

這裏只能看出 obj1obj2 是對象,但不知道具體是哪一個構造函數建立的對象。prototype

但使用 instanceof 以後,就一目瞭然了:code

console.log(obj1 instanceof F1)    // true
console.log(obj1 instanceof F2)    // false
console.log(obj2 instanceof F2)    // true

根據 MDN 的描述:對象

instanceof 運算符用於檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。

instanceof 運算符有兩個操做數,左操做數一般是一個實例對象,它的類型能夠是對象或者函數,也能夠是基本類型(這種狀況下不會報錯,但總返回 false),右操做數一般是一個可調用的(callable)對象,咱們能夠直接認爲它的類型應該是一個函數。原型鏈

那麼 instanceof 的實現原理是什麼呢?從定義中咱們能夠看到,它的原理和原型鏈的機制有關,具體地說,它會拿到右操做數的原型對象,而後在左操做數上經過 __proto__ 不斷查找實例的原型鏈,只要右操做數的 prototype 出如今左操做數的原型鏈上時,就返回 true。若是原型鏈一直查找到盡頭 —— 也就是 null,尚未找到右操做數的原型,就返回 false字符串

因此,在模擬實現中,咱們只要不斷遍歷左操做數的原型鏈,取得原型鏈上的原型對象,並與右操做數的原型對象比較便可。原型

下面是具體的代碼實現:string

function myInstanceof(instance,constructor){
    if(typeof instance != 'object' && typeof instance != 'function' || instance == null){
        return false
    }
    if(typeof constructor != 'function'){
        throw TypeError('the right-hand-side of instanceof must be a function')
    }
    let proto = constructor.prototype
    let p = instance.__proto__
    while(p != null){
        if(p == proto){
            return true
        }
        p = p.__proto__
    }
}

這裏還能夠稍微扯一下題外話。原生的 instanceof 並不支持檢測基本數據類型,就和上面的實現同樣,當發現左操做數是基本數據類型時,會直接返回 false。有沒有辦法作到讓它也能檢測基本數據類型呢?其實是能夠的。

根據規範的說法,在調用 instanceof 的時候,實際上會去調用內部的 @@hasInstance 方法,而這個內部方法在 ES6 中經過 [Symbol.hasInstance] 暴露出來,做爲右操做數(構造函數)上的靜態方法,這意味着咱們能夠修改這個方法,自定義 instanceof 的返回值。

舉個例子,這裏要檢測 1 instanceof Number,那麼咱們能夠經過 Object.defineProperty改寫 Number[Symbol.hasInstance] 方法:

Object.defineProperty(Number,Symbol.hasInstance,{
    value: fucntion(x){
        return typeof(x)==='object'? x instanceof Number : typeof(x) === 'number'
    }                
})

當調用 1 instanceof Number的時候,實際是調用了 Number[Symbol.hasInstance](1),並且它既能夠檢測基本數據類型"number",也能夠檢測它的包裝對象:

1 instanceof Number                          // true
new Number(1) instanceof Number              // true
Number[Symbol.hasInstance](1)                // true
Number[Symbol.hasInstance](new Number(1))    // true

若是不但願修改內置類,也能夠本身實現一個 MyNumber 類:

class MyNumber{
    static [Symbol.hasInstance](x){
         return typeof(x)==='object'? x instanceof Number : typeof(x) === 'number'
    }
}

效果是同樣的:

1 instanceof MyNumber                          // true
new Number(1) instanceof MyNumber              // true
MyNumber[Symbol.hasInstance](1)                // true
MyNumber[Symbol.hasInstance](new Number(1))    // true
相關文章
相關標籤/搜索