← 返回题目列表

如何手写 instanceof?

高频 中等 第 7 / 27 题 更新于 2026/07/28
手写代码instanceof原型链

简化版

常规情况下,left instanceof Right 会判断 Right.prototype 是否出现在 left 的原型链上:从 Object.getPrototypeOf(left) 开始逐层向上找,命中返回 true,到 null 返回 false。但完整语义还允许右侧通过 Symbol.hasInstance 自定义判断。

详细版

只覆盖普通构造函数的基础版:

function ordinaryInstanceof(left, Right) {
  if (typeof Right !== 'function') throw new TypeError('Right must be callable')
  if (left === null || (typeof left !== 'object' && typeof left !== 'function')) {
    return false
  }

  const target = Right.prototype
  if ((typeof target !== 'object' && typeof target !== 'function') || target === null) {
    throw new TypeError('Right.prototype must be an object')
  }

  for (let current = Object.getPrototypeOf(left); current !== null; current = Object.getPrototypeOf(current)) {
    if (current === target) return true
  }
  return false
}

这解释了原型链主体,却没覆盖自定义 Symbol.hasInstance 和绑定函数。面试回答应主动区分“普通构造器算法”和完整运算符语义。

完整版教学

一、instanceof 判断的是原型身份

function Person() {}
const ada = new Person()

ada instanceof Person // true

原因不是对象里存了字符串类型,而是:

Object.getPrototypeOf(ada) === Person.prototype
左侧右侧常见结果
new Person()Persontrue
{}Personfalse
1Numberfalse
Object.create(Person.prototype)Persontrue

最后一行说明 instanceof 通常不能证明对象真的执行过构造函数;只要原型身份吻合就可能为真。

完整算法会先查右侧的 Symbol.hasInstance。只有没有自定义方法时,才退回普通函数的原型链判断,不能把后者误写成运算符的全部语义。

二、普通原型链算法

function ordinaryInstanceof(left, Right) {
  if (typeof Right !== 'function') throw new TypeError('Right must be callable')

  if (left === null || (typeof left !== 'object' && typeof left !== 'function')) {
    return false
  }

  const target = Right.prototype
  if (target === null || (typeof target !== 'object' && typeof target !== 'function')) {
    throw new TypeError('Right.prototype must be an object')
  }

  let current = Object.getPrototypeOf(left)
  while (current !== null) {
    if (current === target) return true
    current = Object.getPrototypeOf(current)
  }
  return false
}

必须从左侧的原型开始,而不是从左侧本身开始。比较必须使用对象身份,不能比较构造器名称或把原型对象做深比较。

三、补上 Symbol.hasInstance

右侧只要是对象,就有机会提供自定义方法:

class EvenNumber {
  static [Symbol.hasInstance](value) {
    return typeof value === 'number' && value % 2 === 0
  }
}

2 instanceof EvenNumber // true

较完整的教学实现为:

function myInstanceof(left, right) {
  const rightIsObject = right !== null &&
    (typeof right === 'object' || typeof right === 'function')
  if (!rightIsObject) throw new TypeError('right operand must be an object')

  const method = right[Symbol.hasInstance]
  if (method != null) {
    if (typeof method !== 'function') {
      throw new TypeError('Symbol.hasInstance must be callable')
    }
    return Boolean(Reflect.apply(method, right, [left]))
  }

  return ordinaryInstanceof(left, right)
}

普通函数从 Function.prototype 继承了默认的 Symbol.hasInstance,调用它最终仍执行普通构造器检查;自定义静态方法则能完全改变判定标准。

四、异常边界为什么重要

({}) instanceof 1           // TypeError:右侧不是对象
({}) instanceof {}          // TypeError:没有 hasInstance,且右侧不可调用
({}) instanceof (() => {})  // TypeError:箭头函数没有对象类型的 prototype

“右侧必须是构造函数”是便于记忆的说法,但更精确的入口是:右侧先要是对象;若有可调用的 @@hasInstance 就使用它,否则必须是函数并走普通算法。

函数可调用也不代表可构造。箭头函数可调用,却没有正常的 prototype 对象,因此默认检查会抛错。

五、修改 prototype 会改变未来结果

function A() {}
const oldObject = new A()

A.prototype = {}
const newObject = new A()

oldObject instanceof A // false
newObject instanceof A // true

instanceof 在判断当下读取当前的 A.prototype。旧实例仍指向旧原型对象,因此替换整个 prototype 会让旧实例不再匹配。若只是给原型新增方法而不替换对象,身份不变,结果也不变。

这也是为什么不能缓存“构造时的类名”来模拟 instanceof

六、跨 realm、绑定函数与品牌检查

iframe 等不同 realm 有各自的内建构造器和原型。另一个窗口创建的数组可能不满足当前窗口的 value instanceof Array,因为两个 Array.prototype 不是同一对象;数组检测应优先 Array.isArray(value)

绑定函数的默认 instanceof 会穿透到目标函数:

function User() {}
const BoundUser = User.bind(null)
new User() instanceof BoundUser // true

另外,伪造 Object.create(C.prototype) 能通过普通检查,却不具有类的私有字段品牌。安全或能力判断不应只依赖 instanceof;内建品牌检查、私有字段 #x in obj 或显式校验往往更可靠。

七、常见误区与追问

  • 误区:instanceof 能证明构造函数确实执行过。 手动把对象原型设为 Ctor.prototype 也能通过普通检查。
  • 误区:右侧只要 typeof === 'function' 就不会报错。 箭头函数虽可调用,但默认算法读取到非对象 prototype 时仍会抛错。
  • 误区:只沿原型链查找就是完整实现。 运算符会优先调用右侧的 Symbol.hasInstance
  • 追问:为什么基本类型左值返回 false? 普通算法只在对象原型链上查找,原始值没有可遍历的对象原型链。
  • 追问:为什么跨 iframe 的数组判断会失败? 每个 realm 拥有不同的 Array.prototype 对象,身份比较不相等。
  • 追问:重设构造函数 prototype 后旧实例怎样? 旧实例仍连着旧原型,因此通常不再满足对新 prototype 的检查。
  • 追问:类型判断应该一律用 instanceof 吗? 不应;数组用 Array.isArray,结构数据可做字段校验,安全品牌要用更可靠机制。

八、加强记忆

  1. 主体:普通算法查的是 Right.prototype 在不在左值原型链。
  2. 起点:从 Object.getPrototypeOf(left) 开始,不比较 left 本身。
  3. 优先级:完整运算符先看 Symbol.hasInstance
  4. 异常:右侧非对象、不可调用或 prototype 非对象都可能抛 TypeError
  5. 身份:替换 prototype 会影响旧实例,跨 realm 原型身份也不同。
  6. 边界instanceof 不是可靠的构造证明,更不是通用安全品牌检查。