这是JS 原生方法原理探究系列的第五篇文章。本文会介绍如何实现 instanceof
方法。
typeof
操作符返回一个表示数据类型的字符串,它可以应付常规场景下的数据类型判断。对基本数据类型 undefined
、 boolean
、string
、 number
、Symbol
和引用数据类型 function
都可以正确判断,但是对 null、数组、对象等则统一返回 "object"。
比如说:
function F1(){}
function F2(){}
const obj1 = new F1()
const obj2 = new F2()
typeof obj1 // ‘object’
typeof obj2 // 'object'
这里只能看出 obj1
和 obj2
是对象,但不知道具体是哪个构造函数创建的对象。
但使用 instanceof
之后,就一目了然了:
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
。
所以,在模拟实现中,我们只要不断遍历左操作数的原型链,取得原型链上的原型对象,并与右操作数的原型对象比较即可。
下面是具体的代码实现:
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。