[JS手写题001] 手写实现 instanceof ?如何欺骗instanceof ?

3 阅读2分钟

在日常开发中,我们经常用 instanceof 来判断类型,比如 [] instanceof Array === true。
请回答:

  1. 用最简练的代码或逻辑,说出 a instanceof B 在 JS 引擎内部的查找算法到底是什么?
  2. 在不改变数组字面量 [] 本身的任何属性(即不碰 [] 的原型链和对象本身),并且不重新声明 [] 的前提下,给我提出两种完全不同的方法,让 [] instanceof Array 的结果变成 false。

参考答案

  • instanceof核心就是遍历左操作数的原型链,去比对右操作数的 prototype 属性
    • 关键的第一步判断:
      instanceof 的左操作数如果是一个基础类型(比如 1 instanceof Number),应该直接返回 false。a.__proto__ 会对基础类型进行隐式装箱(比如转成 Number 对象),如果接着顺着原型链找,就会得出错误的结果(虽然在严格模式下或某些引擎中行为有异,但标准规范要求基础类型直接返回 false)。
    • 永远不要在生产代码里直接写 __proto__
      这是一个历史遗留的、已被废弃的属性,尽管现代浏览器为了兼容性保留了它。严谨的写法应该是 Object.getPrototypeOf(a)。
function myInstanceof(left, right) {
    // 1. 基本数据类型直接返回 false
    if (typeof left !== 'object' || left === null) return false;
    
    // 2. 拿到右操作数的原型对象
    let prototype = right.prototype;
    // 3. 拿到左操作数的原型指针
    let proto = Object.getPrototypeOf(left);
    
    // 4. 开始顺藤摸瓜
    while (true) {
        if (proto === null) return false; // 找到头了也没有
        if (proto === prototype) return true; // 找到了
        proto = Object.getPrototypeOf(proto); // 继续往上找
    }
}

第二部分:欺骗 instanceof(破坏性测试)

方法一:修改 Array.prototype

Array.prototype = {}; 
console.log([] instanceof Array); // false

方法二:利用 ES6 的元编程武器 Symbol.hasInstance

ES6 为了让开发者有能力自定义 instanceof 的行为,提供了一个特殊的 Symbol 属性:Symbol.hasInstance
当引擎执行 obj instanceof Constructor 时,它会首先去检查 Constructor 上有没有 [Symbol.hasInstance] 这个方法。如果有,引擎会直接调用这个方法,并把左操作数传进去,这个方法的返回值就是最终结果,它会直接短路掉刚才那套顺藤摸瓜的查找逻辑!

// 重写 Array 构造函数上的 Symbol.hasInstance 方法
Object.defineProperty(Array, Symbol.hasInstance, {
    value: function(instance) {
        return false; // 我不管你传进来什么,老子直接返回 false
    }
});

console.log([] instanceof Array); // false 绝对生效!