- 文档
- 教程
- 前端
【免费下载链接】zh.javascript.info
现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。
导读
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 ); // falserabbit是在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 相关知识。
相关推荐
JavaScript 的 instanceof 之谜:为什么对象不是由 B() 创建的,`a instanceof B` 却返回 true
JavaScript 的 instanceof 之谜:为什么对象不是由 B 创建的, a instanceof B 却返回 true instanceof 是
文档/教程前端Modern JavaScript Tutorial 深度解析:为什么 `instanceof` 会给出"奇怪"的结果——原型链才是类型判定的真正依据
Modern JavaScript Tutorial 深度解析:为什么 instanceof 会给出"奇怪"的结果——原型链才是类型判定的真正依据 instan
文档/教程前端解决Agentic中AIFunctionSet的instanceof判定难题
解决Agentic中AIFunctionSet的instanceof判定难题 你是否在使用Agentic框架开发AI工具时,遇到过 AIFunctionSet
AI AgentAPI网关后端开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考