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
相关文章
相关标签/搜索