Skip to content

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 属性值不是对象,instanceofTypeError。这在手写实现中经常被遗漏。

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 都有同样的问题

解决方案按可靠性排序:

  1. Array.isArray() —— 引擎层检测,100% 可靠
  2. Object.prototype.toString.call() —— 比 instanceof 稳定(读取 @@toStringTag),但仍可被篡改
  3. 避免跨 Realm 传递构造函数引用——传数据,不传类型

生产中的 instanceof 使用准则

  • 判断数组:用 Array.isArray,不用 instanceof Array
  • 判断 Promise:用 typeof x?.then === 'function'(thenable 检测),比 instanceof Promise 更稳定
  • 判断 Error:用 instanceof Errorx?.namex?.constructor?.name 做冗余校验
  • 业务自定义类instanceof 是安全的——前提是代码在同一个 Realm 中运行。如果涉及 iframe 通信,用 duck typing 或 tag 字段代替
  • 库/框架代码:不要假设 instanceof 可靠。优先用 duck typing、tag 字段或 Symbol.hasInstance 显式定义检测逻辑