☰
JavaScript 原型链探秘:为什么 `a instanceof B` 返回 `true`?——解析 `instanceof` 的真正判定逻辑
2026/10/7 2:36:24 网站建设 项目流程
  • 文档
  • 教程
  • 前端

【免费下载链接】zh.javascript.info

现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。

项目地址:https://gitcode.com/gh_mirrors/zh/zh.javascript.info
点击查看免费下载

导读

instanceof操作符是 JavaScript 中最常用的类型检查手段之一,但它的判定依据常常被人误解:很多人以为它检查的是“对象是否由某个构造函数创建”。本文以《现代 JavaScript 教程》中"不按套路出牌的 instanceof"练习题为切入点,结合仓库源码(1-js/09-classes/06-instanceof/article.md),完整剖析instanceof的底层执行算法,阐明"真正决定类型的是prototype而不是构造函数"这一核心结论。读完本文,你将能准确预测任意obj instanceof Class表达式的结果,理解Symbol.hasInstance的自定义检查逻辑,并掌握typeof、{}.toString、instanceof三种类型检查手段的适用边界。

一个"反常"的结果:题目还原

在继续之前,先看这道经典练习题的完整代码(见 1-js/09-classes/06-instanceof/1-strange-instanceof/task.md):

function A() {} function B() {} A.prototype = B.prototype = {}; let a = new A(); alert( a instanceof B ); // true

直觉上,a是通过new A()创建的,与构造函数B毫无关系,因此a instanceof B理应返回false。但实际运行结果是true。这个"反常"现象正是理解instanceof内部机制的绝佳入口。

官方解答:instanceof不关心构造函数

题目的官方解答(1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md)一针见血地指出了问题的本质:

instanceof并不关心函数,而是关心函数的与原型链匹配的prototype。 这里a.__proto__ == B.prototype,所以instanceof返回true。 总之,根据instanceof的逻辑,真正决定类型的是prototype,而不是构造函数。

逐步拆解执行过程

第一步:new A()设置对象原型。根据new操作符的语义,当执行new A()时,新对象a的内部属性[[Prototype]]会被赋值为A.prototype当前指向的对象。这一点在教程的《F.prototype》一章中有明确说明(1-js/08-prototypes/02-function-prototype/article.md):

F.prototype属性仅在new F被调用时使用,它为新对象的[[Prototype]]赋值。

第二步:两函数共享同一个 prototype 对象。关键在于这一行:

A.prototype = B.prototype = {};

赋值是从右向左执行的,最终A.prototype与B.prototype指向同一个空对象字面量{},即A.prototype === B.prototype。

第三步:instanceof沿原型链逐级比对。当执行a instanceof B时,标准算法(没有Symbol.hasInstance时的默认逻辑)将B.prototype与a原型链上的每一个对象依次比较:

a.__proto__ === B.prototype ? // a.__proto__ 是那个共享的空对象 {} a.__proto__.__proto__ === B.prototype ? a.__proto__.__proto__.__proto__ === B.prototype ? ...

由于a.__proto__ === A.prototype === B.prototype(三者是同一个对象),第一步比较就命中了,于是instanceof立即返回true。

深入instanceof的完整算法

为了吃透上面的结论,需要完整回顾instanceof的算法(对应 1-js/09-classes/06-instanceof/article.md)。

语法与基本用法

obj instanceof Class

当obj隶属于Class类(或Class的派生类)时返回true。它既可以配合class使用,也可以配合普通构造函数,甚至内建类:

class Rabbit {} let rabbit = new Rabbit(); alert( rabbit instanceof Rabbit ); // true // 构造函数而非 class function Rabbit2() {} alert( new Rabbit2() instanceof Rabbit2 ); // true // 内建类:arr 同时隶属于 Array 和 Object let arr = [1, 2, 3]; alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true(Array 原型继承自 Object)

算法执行的两个分支

obj instanceof Class的执行过程大致如下:

分支一:存在静态方法Symbol.hasInstance。若Class定义了静态的Symbol.hasInstance方法,则直接调用它,以它的返回值为准:

// 设置 instanceOf 检查,并假设具有 canEat 属性的都是 animal class Animal { static Symbol.hasInstance { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true:AnimalSymbol.hasInstance 被调用

Symbol.hasInstance是内建的 well-known symbol 之一,在教程的《Symbol》一章中也有收录(1-js/04-object-basics/08-symbol/article.md)。

分支二:默认的标准逻辑——沿原型链比对prototype。大多数 class 没有定义Symbol.hasInstance,此时算法会从obj.__proto__开始,逐级向上比较:

obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // 如果任意一个答案为 true,则返回 true // 如果已经检查到原型链尾端(null),则返回 false

以继承为例:

class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); alert(rabbit instanceof Animal); // true // rabbit.__proto__ === Animal.prototype(不匹配,rabbit.__proto__ 是 Rabbit.prototype) // rabbit.__proto__.__proto__ === Animal.prototype(匹配!)

下图展示了rabbit instanceof Animal执行过程中,Animal.prototype与原型链各级对象逐一比较的示意(instanceof.svg):

构造函数本身不参与检查

需要特别强调的是:Class的 constructor 自身完全不参与检查。检查只涉及两样东西——obj的原型链和Class.prototype。因此:

  • 等价写法:obj instanceof Class可以改写为Class.prototype.isPrototypeOf(obj)(isPrototypeOf在objA处于objB原型链中时返回true)。
  • 创建对象后再替换prototype属性,会得到"出人意料"的结果:
function Rabbit() {} let rabbit = new Rabbit(); // 修改了 prototype Rabbit.prototype = {}; // ...再也不是 rabbit 了! alert( rabbit instanceof Rabbit ); // false

rabbit是在Rabbit.prototype被替换之前创建的,它的[[Prototype]]仍指向旧的默认 prototype,而instanceof现在拿新Rabbit.prototype去比对整条原型链,自然匹配不上——这从反面再次印证了"决定类型的是 prototype,而不是构造函数"。

从源码角度印证:prototype是连接new与instanceof的唯一桥梁

为什么会出现A.prototype = B.prototype = {}这种"串台"?根源在于 JavaScript 原型继承的经典设计:构造函数的prototype属性是new与instanceof之间的唯一纽带。

  • 在创建对象的方向上,new F()把F.prototype赋给新对象的[[Prototype]](1-js/08-prototypes/02-function-prototype/article.md);
  • 在检查类型的方向上,instanceof反向拿Class.prototype去匹配对象原型链上的节点(1-js/09-classes/06-instanceof/article.md)。

两个方向都以prototype为参照物,构造函数名只是一个"标签"。当两个函数共享同一个prototype对象时,通过new A()创建的对象的原型链上就同时"挂着"B.prototype,a instanceof B自然成立。这并非instanceof的 bug,而是原型链语义的一致体现。

此外,从该章节的源码可以推断:默认情况下每个函数都有F.prototype = { constructor: F },即默认 prototype 自带指向函数自身的constructor属性;但一旦开发者整体替换了prototype(如题目中的A.prototype = B.prototype = {}),这个默认的constructor关联也会随之丢失,a.constructor将不再指向A——这是原型链设计在工程实践中的另一处需要注意的坑。

拓展:三种类型检查手段的对比

教程在该章末尾给出了一个实用的总结表,可用于在实际开发中选择合适的检查手段:

手段用于返回值
typeof原始数据类型string
{}.toString原始数据类型、内建对象、含Symbol.toStringTag属性的对象string
instanceof对象true/false

要点补充:

  • Object.prototype.toString可以被提取出来,借助call(详见 1-js/06-advanced-functions/09-call-apply-decorators/article.md 中讲解的call方法)在任意值上执行,得到[object Number]、[object Array]、[object Null]等精确结果;对象的Symbol.toStringTag属性还可以自定义输出标签。
  • {}.toString从技术上讲是"更高级的typeof";而当需要针对类层次结构做检查、且需要考虑继承关系时,instanceof操作符最为擅长——这正是本练习题的场景。

小结

  • instanceof的判定只与两条原型链的匹配有关:把Class.prototype与obj原型链上的每个节点逐一比对,任一命中即返回true。
  • 构造函数自身不参与检查;A.prototype = B.prototype = {}使a.__proto__ === B.prototype成立,因此a instanceof B返回true。
  • 可通过静态方法Symbol.hasInstance完全自定义instanceof的行为;instanceof等价于Class.prototype.isPrototypeOf(obj)。
  • 创建对象之后再替换构造函数的prototype,会破坏已有对象的instanceof判定,因为对象在创建那一刻就已"锁定"了当时的 prototype。
  • 文档
  • 教程
  • 前端

【免费下载链接】zh.javascript.info

现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。

项目地址:https://gitcode.com/gh_mirrors/zh/zh.javascript.info
点击查看免费下载
上一篇:三步搞定百度网盘macOS插件安装:解除下载限速、点亮SVIP标识
下一篇:百度网盘macOS下载提速补丁零基础上手:三步解除本地限速并点亮SVIP

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询