# instanceof

# 前言

原理:采用 MDN 的说法就是,instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

于是,我们有了第一版代码,循环遍历target的原型链,如果等于origin.prototype,就return true,循环到顶层原型链null的时候,还没有相等就return false。简单归纳就是个类似于链表的遍历

function instanceOf(target, origin) {
  if (target == null) return false; // null undefined直接返回false
  if (!target.__proto__) return false;
  while (target) { // 循环遍历原型链
    if (target.__proto__ === origin.prototype) return true;
    target = target.__proto__;
  }
  return false;
}

但是,我们现在看一下这个例子:

const a = 1;
const str = 'bowlofnoodles';

// 第一版代码实现
instanceOf(a, Number); // true
instanceOf(a, Object); // true
instanceOf(str, String); // true
instanceOf(str, Object); // true

// 实际的浏览器instanceof
a instanceof Object // false
a instanceof Number // false
b instanceof Object // false
b instanceof String // false

分析一下:是因为包装对象的缘故。

astr在进行对象属性的读取即a.__proto__str.__proto__的时候会被包装成一个Number对象,同理str会被包装成一个String对象。

这里提一下:包装类型和引用类型的区别在于对象的生存期。使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前,会一直保存在堆内存中。而后台自动创建的基本包装类型的对象,则只存在一行代码的执行瞬间,然后立即被销毁。这意味着我们不能为基本类型的值添加属性和方法

js中的基本类型包装对象:StringNumberBooleanBigIntSymbol,其实就是除了nullundefined不能访问属性,所以他们没有对应的包装对象,其他都有。详细可以看一下高程红宝书里对包装对象的讲解。

再回来,所以上述代码其实等价于

const a = new Number(1);
const str = new String('bowlofnoodles');

// 第一版代码实现
instanceOf(a, Number); // true
instanceOf(a, Object); // true
instanceOf(str, String); // true
instanceOf(str, Object); // true

那我们怎么实现跟原生一样的效果,其实就是在入口判断一下类型,不是复杂类型的时候就直接 return false 即可。

于是有了第二版,改进一下,就完成啦:

function isComplexType(target) {
  return (
    // 注意避开null
    typeof target === 'object' && typeof target !== 'null'
  ) || typeof target === 'function'; // 注意带上function
}

function instanceOf(target, origin) {
  if (!isComplexType(target)) return false; // 不是复杂类型直接return false
  if (!target.__proto__) return false;
  while (target) { // 循环遍历原型链
    if (target.__proto__ === origin.prototype) return true;
    target = target.__proto__;
  }
  return false;
}
Last Updated: 6/25/2021, 6:35:25 AM