手写 `instanceof`:如何通过遍历原型链判断构造函数

手写 instanceof:如何通过遍历原型链判断构造函数

大家好,欢迎来到今天的讲座。今天我们来深入探讨一个看似简单但非常重要的 JavaScript 概念——instanceof 运算符的底层实现原理

你可能每天都在用 instanceof,比如:

const obj = new Person();
console.log(obj instanceof Person); // true

但你有没有想过:它是怎么知道 obj 是不是 Person 的实例?
它背后是不是有一个“查找过程”?这个过程是否可以被我们手动模拟?

今天我们就从零开始,手写一个类似 instanceof 的功能,理解它的本质,并掌握原型链在其中扮演的关键角色。


一、什么是 instanceof

首先明确一下定义:

instanceof 是 JavaScript 中用于检测一个对象是否属于某个构造函数创建的实例的运算符。

语法如下:

left instanceof right
  • left 是要检测的对象;
  • right 是构造函数(或类);
  • 返回布尔值:如果 leftright 的实例,则返回 true,否则返回 false

示例说明:

function Animal(name) {
  this.name = name;
}

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);

const dog = new Dog("旺财", "金毛");

console.log(dog instanceof Dog);     // true
console.log(dog instanceof Animal);  // true
console.log(dog instanceof Object);  // true

可以看到,dog 不仅是 Dog 的实例,也是 AnimalObject 的实例 —— 因为原型链继承的关系。

这正是我们要研究的核心:为什么 dog instanceof Animal 会返回 true

答案就是:原型链查找机制!


二、原型链的本质与作用

在讲解之前,先回顾几个关键知识点:

概念 描述
prototype 构造函数的一个属性,指向该构造函数所有实例共享的原型对象
__proto__(或 Symbol.toStringTag 实例对象内部指向其构造函数的 prototype 的指针(即原型链上的上一级)
原型链 从实例对象出发,沿着 __proto__ 向上找,直到找到 null 为止的一条链

让我们用代码演示一下:

function Foo() {}
Foo.prototype.bar = 'hello';

const fooInstance = new Foo();

console.log(fooInstance.__proto__ === Foo.prototype); // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

这就是所谓的原型链:
fooInstance → Foo.prototype → Object.prototype → null


三、instanceof 的工作原理(理论)

当执行 obj instanceof Constructor 时,JavaScript 引擎做了以下事情:

  1. 获取 Constructor.prototype
  2. obj.__proto__ 开始,逐级向上遍历原型链;
  3. 如果在某一层找到了等于 Constructor.prototype 的对象,则返回 true
  4. 如果遍历到顶层(即 null)还没找到,则返回 false

换句话说,instanceof 就是在做一次 原型链查找匹配

✅ 关键点总结:

  • 它不关心构造函数本身,只关心构造函数的 .prototype 是否出现在原型链中;
  • 它不会检查构造函数名或其他属性;
  • 它依赖于 __proto__ 的存在(ES6 后可通过 Object.getPrototypeOf() 替代);

四、手写一个 myInstanceof 函数(核心逻辑)

现在我们来动手实现一个等效于 instanceof 的函数:

function myInstanceof(left, right) {
  // 参数校验
  if (typeof left !== 'object' || left === null) {
    return false;
  }

  // 获取右侧构造函数的 prototype 属性
  const prototype = right.prototype;

  // 从左端对象开始,沿着原型链向上查找
  let current = left;

  while (current !== null) {
    if (current === prototype) {
      return true;
    }
    current = Object.getPrototypeOf(current); // 或者 current.__proto__
  }

  return false;
}

💡 注意事项:

  • 使用 Object.getPrototypeOf() 而非 __proto__ 更规范(兼容性更好);
  • 对于非对象类型直接返回 false(如 null, undefined, number 等);
  • 遍历过程中一旦命中 prototype,立即返回 true
  • 若走到链尾仍没找到,返回 false

五、测试我们的 myInstanceof

我们用前面的例子验证一下:

function Animal(name) {
  this.name = name;
}

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);

const dog = new Dog("旺财", "金毛");

console.log(myInstanceof(dog, Dog));     // true
console.log(myInstanceof(dog, Animal));  // true
console.log(myInstanceof(dog, Object));  // true
console.log(myInstanceof(dog, String));  // false

结果完全一致!

再试试边界情况:

console.log(myInstanceof(null, Object));   // false
console.log(myInstanceof(undefined, Object)); // false
console.log(myInstanceof(42, Number));     // false(不是对象)
console.log(myInstanceof([], Array));      // true
console.log(myInstanceof({}, Object));     // true

完美符合预期!


六、深入对比:原生 instanceof vs 手写版本

特性 原生 instanceof 手写 myInstanceof
性能 快速(引擎优化) 较慢(JS 层循环)
可读性 简洁直观 易懂易改
控制权 不可控 自主控制逻辑
支持 Symbol.toPrimitive ❌(需额外处理)
支持 Proxy 对象 ❌(可能失效)

📌 小贴士:虽然手写版本不能替代原生 instanceof(性能差),但它帮助我们彻底理解了它的运行机制,非常适合教学和调试场景。


七、进阶问题:如何处理特殊对象?

有些对象比较特殊,比如 Symbol.iteratorProxyMapSet 等,它们的原型链可能隐藏得很深或者被代理拦截。

举个例子:

const proxy = new Proxy({}, {
  get(target, prop) {
    console.log(`访问 ${prop}`);
    return target[prop];
  }
});

console.log(proxy instanceof Object); // true(正常)

此时 proxy.__proto__ 仍然是 Object.prototype,所以 instanceof 正常工作。

但如果用了 Proxy 修改了原型链呢?

const handler = {
  get(target, prop) {
    if (prop === '__proto__') {
      return null; // 模拟破坏原型链
    }
    return target[prop];
  }
};

const brokenProxy = new Proxy({}, handler);
console.log(brokenProxy instanceof Object); // false(因为 __proto__ 被拦截为 null)

⚠️ 这说明:如果你手动修改了原型链(尤其是 __proto__),可能会导致 instanceof 行为异常。

这也是为什么现代开发中推荐使用 Object.prototype.toString.call() 来判断类型(更可靠):

function getType(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1);
}

console.log(getType([]));     // "Array"
console.log(getType(new Date())); // "Date"
console.log(getType(null));   // "Null"

不过这是另一个话题了 😊


八、实战建议:什么时候该用 instanceof

场景 推荐方式 理由
判断对象是否为某类实例 instanceof 清晰、语义明确
判断数据类型(如数组、日期) Object.prototype.toString.call() 更安全、不受原型链干扰
类型守卫(TypeScript) ✅ 类型断言 + instanceof TypeScript 编译期可优化
动态判断构造函数 ✅ 手写 myInstanceof 可扩展性强,适合框架开发

例如,在 React 组件中判断是否是 ReactElement:

if (element instanceof ReactElement) {
  // 处理 React 元素
}

或者在自定义库中判断传入参数类型:

function processInput(input) {
  if (!myInstanceof(input, MyCustomClass)) {
    throw new Error('Expected an instance of MyCustomClass');
  }
  // ...
}

九、常见误区澄清

误区 解释
instanceof 只看构造函数名字 ❌ 错!它看的是 prototype 是否存在于原型链中
new Date() instanceof Date 是因为 Date 构造函数 ✅ 正确,但本质还是原型链匹配
[] instanceof Object 为什么是 true? ✅ 因为 Array.prototype.__proto__ === Object.prototype
Object.create(null) 能被 instanceof Object 判断吗? ❌ 不行!因为没有原型链,Object.getPrototypeOf(obj) 返回 null

十、总结:掌握 instanceof 的意义

今天我们完成了以下任务:

  1. ✅ 理解了 instanceof 的底层机制:原型链查找;
  2. ✅ 手动实现了 myInstanceof 函数,加深对原型链的理解;
  3. ✅ 分析了各种边界情况和陷阱;
  4. ✅ 提供了实际应用建议和替代方案。

🎯 最重要的一点是:不要把 instanceof 当作黑盒使用,而要把它当作一种“原型链匹配算法”的体现。

这样你在遇到以下问题时就能快速定位原因:

  • “为什么我的对象不是某个类的实例?”
  • “为什么 instanceof 返回了 false?”
  • “如何安全地进行类型判断?”

📝 附录:完整代码清单(可复制粘贴)

function myInstanceof(left, right) {
  if (typeof left !== 'object' || left === null) {
    return false;
  }

  const prototype = right.prototype;
  let current = left;

  while (current !== null) {
    if (current === prototype) {
      return true;
    }
    current = Object.getPrototypeOf(current);
  }

  return false;
}

// 测试用例
function Animal(name) { this.name = name; }
function Dog(name, breed) { Animal.call(this, name); this.breed = breed; }
Dog.prototype = Object.create(Animal.prototype);

const dog = new Dog("旺财", "金毛");
console.log(myInstanceof(dog, Dog));     // true
console.log(myInstanceof(dog, Animal));  // true
console.log(myInstanceof(dog, Object));  // true
console.log(myInstanceof(dog, String));  // false
console.log(myInstanceof(null, Object)); // false

希望这篇讲解对你有启发!如果你正在准备面试、重构项目、或者只是想更深入理解 JS,记住:原型链是你必须掌握的核心概念之一。

祝你编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注