在日常开发中,我们经常用 instanceof 来判断类型,比如 [] instanceof Array === true。
请回答:
- 用最简练的代码或逻辑,说出 a instanceof B 在 JS 引擎内部的查找算法到底是什么?
- 在不改变数组字面量 [] 本身的任何属性(即不碰 [] 的原型链和对象本身),并且不重新声明 [] 的前提下,给我提出两种完全不同的方法,让 [] 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 绝对生效!