ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaScript 原型链探秘:为什么 `a instanceof B` 返回 `true`?——解析 `instanceof` 的真正判定逻辑

JavaScript 原型链探秘:为什么 `a instanceof B` 返回 `true`?——解析 `instanceof` 的真正判定逻辑 文档教程前端【免费下载链接】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.mdfunction 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.mdF.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 ); // trueArray 原型继承自 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); // trueAnimalSymbol.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.prototypea instanceof B自然成立。这并非instanceof的 bug而是原型链语义的一致体现。此外从该章节的源码可以推断默认情况下每个函数都有F.prototype { constructor: F }即默认 prototype 自带指向函数自身的constructor属性但一旦开发者整体替换了prototype如题目中的A.prototype B.prototype {}这个默认的constructor关联也会随之丢失a.constructor将不再指向A——这是原型链设计在工程实践中的另一处需要注意的坑。拓展三种类型检查手段的对比教程在该章末尾给出了一个实用的总结表可用于在实际开发中选择合适的检查手段手段用于返回值typeof原始数据类型string{}.toString原始数据类型、内建对象、含Symbol.toStringTag属性的对象stringinstanceof对象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点击查看免费下载相关推荐JavaScript 的 instanceof 之谜为什么对象不是由 B() 创建的a instanceof B 却返回 trueJavaScript 的 instanceof 之谜为什么对象不是由 B 创建的 a instanceof B 却返回 true instanceof 是文档/教程前端Modern JavaScript Tutorial 深度解析为什么 instanceof 会给出奇怪的结果——原型链才是类型判定的真正依据Modern JavaScript Tutorial 深度解析为什么 instanceof 会给出奇怪的结果——原型链才是类型判定的真正依据 instan文档/教程前端解决Agentic中AIFunctionSet的instanceof判定难题解决Agentic中AIFunctionSet的instanceof判定难题 你是否在使用Agentic框架开发AI工具时遇到过 AIFunctionSetAI AgentAPI网关后端开发工具上一篇三步搞定百度网盘macOS插件安装解除下载限速、点亮SVIP标识下一篇百度网盘macOS下载提速补丁零基础上手三步解除本地限速并点亮SVIP创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表