Skip to content
instanceof:原型链查找,不是类型检查
instanceof 的语义常被误读为“判断 A 是否由 B 构造”。实际规则更简单:判断 B.prototype 是否存在于 A 的原型链上。这个过程与 new B() 的构造过程无关。
概述
instanceof 运算符在 ECMAScript 中执行原型链检查,而非传统意义上的类型检查。理解它的行为有助于处理原型继承、跨执行上下文问题以及自定义检测逻辑。
基本概念
原型链是 JavaScript 对象之间继承关系的实现。每个对象有一个内部 [[Prototype]] 链接指向它的原型。instanceof 所做的就是沿着这个链向上查找,直到找到匹配的 prototype 对象或链到达 null。
工作原理
规范步骤
ECMAScript 规范 §13.15.2 定义了 instanceof 的运行时语义:
- 取 RightHandSideExpression 的值作为 Target(构造函数)。
- 若 Target 没有
[[HasInstance]]内部方法,抛出TypeError。 - 调用
Target.[[HasInstance]](V)。
默认的 [[HasInstance]] 实现由 Function.prototype[Symbol.hasInstance] 提供(ES6 引入):
- 若
this(Target)不可调用,返回false。 - 调用
OrdinaryHasInstance(Target, V)。 OrdinaryHasInstance执行:- 获取
Target.prototype,赋值给 P。 - 若 P 不是对象,抛出
TypeError。 - 从 V 开始,沿原型链向上遍历:
V = V.[[Prototype]]- 若
V === null,返回false - 若
V === P,返回true
- 获取
V8 实现路径
在 V8 源码中,OrdinaryHasInstance 的核心逻辑:
cpp
// src/objects/objects.cc
bool JSObject::HasInstance(Handle<JSObject> object, Handle<Object> instance) {
Handle<Object> prototype = JSObject::GetProperty(object, "prototype");
Handle<Object> proto = instance;
while (!proto->IsNull()) {
if (proto.is_identical_to(prototype)) return true;
proto = Object::GetPrototype(proto);
}
return false;
}每次比较都直接比对 prototype 对象的堆地址,因此不同执行环境下的同名构造函数无法通过 instanceof 互认。
Symbol.hasInstance:自定义检测逻辑
构造函数可以通过 Symbol.hasInstance 覆盖默认的原型链查找:
js
class EvenNumber {
static [Symbol.hasInstance](instance) {
return typeof instance === 'number' && instance % 2 === 0;
}
}
2 instanceof EvenNumber; // true
3 instanceof EvenNumber; // false只要定义在类上的 static [Symbol.hasInstance] 方法,instanceof 就会优先使用它。
对于内置构造函数(如 Array),也可以直接添加自有属性进行覆盖:
js
Array[Symbol.hasInstance] = () => false;
[] instanceof Array; // falseFunction.prototype[Symbol.hasInstance] 是所有函数共享的默认实现。直接在 Array 上设置 [Symbol.hasInstance] 会创建自有属性,该属性优先于原型上的方法被调用。这种情况下,instanceof Array 的行为将完全由新方法控制。
若修改 Function.prototype[Symbol.hasInstance],则会波及所有函数的 instanceof 行为,影响范围极大,不应轻易使用。
基本用法
手写 instanceof 的边界条件
js
function myInstanceof(obj, constructor) {
// 1. 右侧必须是可调用对象
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. 原始类型(null 以及非对象非函数)直接返回 false
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 fn === 'function'),必须参与原型链查找,因此第 3 步中不能遗漏对'function'的判断。 constructor.prototype可能不是对象。规范要求此类情况直接抛出TypeError,大多数手动实现会忽略这一点。- 对于 Proxy 对象,
Object.getPrototypeOf(proxy)会直接调用代理的内部[[GetPrototypeOf]]方法,从而触发getPrototypeOf陷阱。instanceof的结果取决于陷阱的返回值,规范并未在调用OrdinaryHasInstance前对代理解包。如果陷阱返回了一个与原本原型链不相符的值,会导致检测结果偏离预期。
示例
原型链关联,非构造关联
js
function A() {}
function B() {}
B.prototype = Object.create(A.prototype);
const obj = {};
// obj 从未被任何构造函数创建
Object.setPrototypeOf(obj, B.prototype);
obj instanceof B; // true
obj instanceof A; // true (B.prototype 的原型是 A.prototype)该结果与 new 无关,纯粹由 [[Prototype]] 链条决定。
注意点:跨 Realm 的问题
不同的 JavaScript 执行上下文(iframe、Worker、VM 上下文)拥有独立的内置构造函数和原型对象:
js
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = new iframe.contentWindow.Array(1, 2, 3);
iframeArray instanceof Array; // false
Array.isArray(iframeArray); // trueinstanceof Array 返回 false 是因为当前 window 的 Array.prototype 与 iframe 的 Array.prototype 是不同的堆对象,即使它们功能完全一致。这一点对 Date、RegExp、Error、Map、Set 等所有内置类型同样适用。
针对这类场景,可以按可靠性依次选择:
Array.isArray()—— 引擎层检测,不受上下文隔离影响。Object.prototype.toString.call()—— 比instanceof稳定,但会读取Symbol.toStringTag,可被篡改。- 避免跨 Realm 传递构造函数引用,尽量只传递数据。
应用
instanceof 使用准则
- 判断数组:优先使用
Array.isArray,而不是instanceof Array。 - 判断 Promise:使用
typeof x?.then === 'function'进行 thenable 检测,这比instanceof Promise更通用。 - 判断 Error:使用
instanceof Error,并可附加x?.name或x?.constructor?.name作为冗余校验。 - 自定义类:在同一个执行上下文内,
instanceof是安全的。若涉及跨 iframe 通信,则应改用 duck typing 或显式的识别字段。 - 库/框架代码:不应假设
instanceof可靠,优先使用 duck typing、识别字段或通过Symbol.hasInstance自定义检测逻辑。
