Appearance
instanceof:原型链查找,不是类型检查
instanceof 的语义经常被误解为"判断 A 是不是 B 创建的实例"。实际上它的逻辑更简单也更根本:判断 B.prototype 是否出现在 A 的原型链上。 这与构造过程无关——你没有调用 new B() 也能让 instanceof 返回 true。
规范与 V8 实现路径
ECMAScript 规范 §13.15.2 定义了 instanceof 的运行时语义,核心步骤:
text
1. 获取 RightHandSideExpression 的值 → Target(构造函数)
2. 如果 Target 没有 [[HasInstance]] 内部方法 → TypeError
3. 调用 Target.[[HasInstance]](V)[[HasInstance]] 的默认实现(Function.prototype[@@hasInstance],ES6 新增):
text
1. 如果 this(Target)不可调用 → 返回 false
2. 调用 OrdinaryHasInstance(Target, V)
3. OrdinaryHasInstance:
a. 获取 Target.prototype → P
b. 如果 P 不是对象 → TypeError
c. 沿 V 的原型链向上遍历:
- V = V.[[Prototype]]
- 如果 V === null → 返回 false
- 如果 V === P → 返回 true在 V8 中,OrdinaryHasInstance 的核心逻辑:
cpp
// src/objects/objects.cc
bool JSObject::HasInstance(Handle<JSObject> object, Handle<Object> instance) {
// 1. 获取构造函数的 prototype
Handle<Object> prototype = JSObject::GetProperty(object, "prototype");
// 2. 沿原型链遍历
Handle<Object> proto = instance;
while (!proto->IsNull()) {
if (proto.is_identical_to(prototype)) return true;
proto = Object::GetPrototype(proto);
}
return false;
}Symbol.hasInstance:自定义行为与内置类的保护
ES6 引入了 Symbol.hasInstance,允许构造函数自定义 instanceof 的行为:
js
class EvenNumber {
static [Symbol.hasInstance](instance) {
return typeof instance === 'number' && instance % 2 === 0;
}
}
2 instanceof EvenNumber; // true
3 instanceof EvenNumber; // false内置类(Array、Date、RegExp 等)的 Symbol.hasInstance 定义了但不可写:
js
Array[Symbol.hasInstance] = () => false;
[][Symbol.hasInstance] = () => false; // 这也是尝试修改 Array.prototype 上的
[]; // 无效
// 原因:Array 的 Symbol.hasInstance 通过原型链继承自 Function.prototype[@@hasInstance]
// 直接在 Array 上设置会创建自有属性,但 instanceof 在规范化路径中读取 Function.prototype[@@hasInstance]实际上,Function.prototype[Symbol.hasInstance] 是可写的(writable: true),但修改它会破坏所有函数(包括内置类)的 instanceof 行为——这是一个"核弹级"操作,永远不要做。
对于自定义类,Symbol.hasInstance 是有用的元编程工具。但要注意:如果 Symbol.hasInstance 的方法定义在 class 上(static),ES6 class 不可被普通调用,所以 instanceof 的行为完全由 Symbol.hasInstance 控制。
手写 instanceof 的边界条件
js
function myInstanceof(obj, constructor) {
// 1. constructor 必须是函数
if (typeof constructor !== 'function') {
throw new TypeError('Right-hand side of instanceof is not callable');
}
// 2. 检查 Symbol.hasInstance
const hasInstance = constructor[Symbol.hasInstance];
if (typeof hasInstance === 'function') {
return !!hasInstance.call(constructor, obj);
}
// 3. 原始类型直接返回 false(除了通过 Object() 装箱的)
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
return false;
}
// 4. 获取 prototype 并遍历原型链
const prototype = constructor.prototype;
if (typeof prototype !== 'object') {
throw new TypeError('prototype is not an object');
}
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}几个关键边界:
typeof obj !== 'object' 不排除函数。 函数也是对象(typeof fn === 'function'),也应该参与原型链查找。正确的检查是 typeof obj !== 'object' && typeof obj !== 'function'。
constructor.prototype 可能不是对象。 规范要求如果 prototype 属性值不是对象,instanceof 抛 TypeError。这在手写实现中经常被遗漏。
Proxy 会干扰原型链遍历。 Object.getPrototypeOf(proxy) 会触发 Proxy 的 getPrototypeOf 陷阱。如果陷阱返回的值与真实原型不一致,instanceof 的结果会被误导。规范要求 instanceof 对 Proxy 解包——先调用 OrdinaryHasInstance,如果 Target 是 Proxy,用 Proxy 的 target 做检查。
跨 Realm:instanceof 的最大盲区
不同 iframe/worker/vm context 各自拥有独立的内置构造函数和原型对象。iframe 的 Array.prototype 和当前 window 的 Array.prototype 是不同的堆对象:
js
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = new iframe.contentWindow.Array(1, 2, 3);
iframeArray instanceof Array; // false(window.Array.prototype ≠ iframe.Array.prototype)
Array.isArray(iframeArray); // true(检查内部 instance_type)
// Date、RegExp、Error、Map、Set 都有同样的问题解决方案按可靠性排序:
Array.isArray()—— 引擎层检测,100% 可靠Object.prototype.toString.call()—— 比 instanceof 稳定(读取@@toStringTag),但仍可被篡改- 避免跨 Realm 传递构造函数引用——传数据,不传类型
生产中的 instanceof 使用准则
- 判断数组:用
Array.isArray,不用instanceof Array - 判断 Promise:用
typeof x?.then === 'function'(thenable 检测),比instanceof Promise更稳定 - 判断 Error:用
instanceof Error加x?.name或x?.constructor?.name做冗余校验 - 业务自定义类:
instanceof是安全的——前提是代码在同一个 Realm 中运行。如果涉及 iframe 通信,用 duck typing 或 tag 字段代替 - 库/框架代码:不要假设
instanceof可靠。优先用 duck typing、tag 字段或Symbol.hasInstance显式定义检测逻辑
