【 js手写系列】这可能是你见过的最详细的instanceof模拟实现

304 阅读4分钟

努力让学习成为一种习惯,自信来源于充分的准备

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

instanceof

这里直接引用mdn的描述

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上

语法:object instanceof constructor

我们直接看例子:

const obj = {}
console.log(obj instanceof Object); // true

const arr = []
console.log(arr instanceof Array); // true

function Foo() {}
const f = new Foo()
console.log(f instanceof Foo); // true
console.log(f instanceof Object); // true

相信大家对instanceof概念以及使用有了基本的认知

在具体实现之前,我们结合规范深挖下instanceof的细节

image.png

我们简单翻译一下:

  1. instanceof运算符右边的变量(target)必须是对象类型,否则抛出异常

image.png

2、3. 尝试获取target的内部方法@@hasInstance(本身和继承的),如果有,则调用

这里部分小伙伴可能对@@hasInstance不太了解,这里简单介绍下

当我们执行 obj instaceof Foo的时候,实际上执行的是Foo函数内部的一个静态方法Foo[Symbol.hasInstance](obj),如果函数本身没有定义的话,则会调用Function.prototype[Symbol.hasInstance](obj)(Symbol.hasInstance@@hasInstance是一回事)

Function 实例的  [@@hasInstance]()  方法指定确定构造函数是否将对象识别为其实例的默认过程。它由 instanceof 运算符调用

换句话说:我们可以自定义instanceof的行为。如果没有定义,则采用Function.prototype上定义的默认行为,其中Function.prototype [ @@hasInstance ] (V)调用的是OrdinaryHasInstance

image.png

Function.prototype[@@hasInstance]() 属性是不可配置且不可写的。这是一个安全特性,用于防止绑定函数的底层目标函数被获取

我们直接来看代码加深下理解

// 默认情况
class Tc {}
const tc = new Tc()
console.log(Tc[Symbol.hasInstance](tc) === tc instanceof Tc); // true

// 自定义情况
class MyArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
console.log([] instanceof MyArray); // true
console.log(Function.prototype[Symbol.hasInstance].call(MyArray, [])); // false

4、5.如果没有实现@@hasInstance,而调用OrdinaryHasInstance

这里4、5 两步主要是为了兼容ECMA早期的版本(没有通过@@instance属性去定义instanceof语法语义),结合上面第3步讲到的,我们可以知道OrdinaryHasInstance内部逻辑和目前instanceof默认逻辑即Function.prototype[Symbol.hasInstance]()的判定逻辑是一样的

到这里相信大家对instanceof有了基础的认识了。接下来我们来看看OrdinaryHasInstance内部做了哪些事情,并根据它来实现自己的instanceof

实现

image.png

我们针对整体一些针对参数限制的做处理也就是 13两步

O instanceof C

C如果不是可调用对象即函数,直接返回 false(如果不是对象即基本类型 则抛出TypeError异常,这是前面规范第一步的要求)

O如果不是对象(也就是基本类型),直接返回 false

const isBasicType = (val = '') => {
  const t = Object.prototype.toString.call(val)
  if (t == '[object String]' || 
  t == '[object Boolean]' || 
  t == '[object Number]' || 
  t == '[object Null]' || 
  t == '[object Undefined]' || 
  t == '[object Symbol]') {
     return true;
  }
  return false
}

function myInstanceof(C, O) {
   if(isBasicType(C)) {
      throw new TypeError(`Right-hand side of 'instanceof' is not an object`)
   }
   if (typeof C !== 'function' || (isBasicType(O))) {
     return false
   }
}

接下来我们看最为核心的 4-6步 实现

function myInstanceof(C, O) {
  const p = C.prototype
  if(isBasicType(p)) {
     throw new TypeError(`function prototype is not a object`)
  }
  let oProto = Object.getPrototypeOf(O)
  
  while(oProto) {
    if (oProto === null) return false
    if (oProto === p) {
        return true
    } 
    oProto = Object.getPrototypeOf(oProto)
  }
  return false
}

好了我们看下整体代码

const isBasicType = (val = '') => {
  const t = Object.prototype.toString.call(val)
  if (t == '[object String]' || 
  t == '[object Boolean]' || 
  t == '[object Number]' || 
  t == '[object Null]' || 
  t == '[object Undefined]' || 
  t == '[object Symbol]') {
     return true;
  }
  return false
}

function myInstanceof(C, O) {
  if(isBasicType(C)) {
    throw new TypeError(`Right-hand side of 'instanceof' is not an object`)
  }
  if (typeof C !== 'function' || (isBasicType(O))) {
    return false
  }
  
  const p = C.prototype
  if(isBasicType(p)) {
     throw new TypeError(`function prototype is not a object`)
  }
  let oProto = Object.getPrototypeOf(O)
  
  while(oProto) {
    if (oProto === null) return false
    if (oProto === p) {
        return true
    } 
    oProto = Object.getPrototypeOf(oProto)
  }
  return false
}

我们验证下

console.log(myInstanceof(null, {})); // TypeError
console.log(myInstanceof({}, {})); // false
console.log(myInstanceof(Object, 1)); // false
console.log(myInstanceof(Object, {})); // true
console.log(myInstanceof(Array, [])); // true
console.log(myInstanceof(Object, [])); // true
console.log(myInstanceof(Function, [])); // false

function Func() {}
const f = new Func()
console.log(myInstanceof(Func, f)); // true
console.log(myInstanceof(Object, Func)); // true

function Func2() {}
Func2.prototype = f
const f2 = new Func2()
console.log(myInstanceof(Func2, f2)); // true
console.log(myInstanceof(Func, f2)); // true

console.log(myInstanceof(Object, Function)); // true
console.log(myInstanceof(Object, Array)); // true
console.log(myInstanceof(Function, Array)); // true

没问题,完美😤。但是就。。。完了吗?

还有一点我们需要关注下就是传进来的函数是bind函数的时候

image.png

由规范可知,当函数是bind函数时,会去获取其内部属性[[BoundTargetFunction]]

image.png

function func() {}
const funcBind = func.bind()

const f1 = new func()
const fBind = new funcBind()

console.log(f1 instanceof func); // true
console.log(f1 instanceof funcBind); // true
console.log(fBind instanceof func); // true
console.log(fBind instanceof funcBind); // true

我查了资料没找到获取内部属性[[BoundTargetFunction]]的方法,故上面的测例这份代码无法通过,不过最核心的原理我们已经搞定。也明白了针对bind。js引擎内部是怎么处理的😄

最后

到这里,就是本篇文章的全部内容了

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

如果你有疑问或者出入,评论区告诉我,我们一起讨论

参考文章

MDN instanceof