ARTICLE DETAIL

资讯详情

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

Modern JavaScript 教程实战精讲:delete 与原型链——解密“Working with prototype“中的属性查找与删除规则

Modern JavaScript 教程实战精讲:delete 与原型链——解密“Working with prototype“中的属性查找与删除规则 文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载导读本文以 Modern JavaScript Tutorialen.javascript.info原型继承章节的高难度任务Working with prototypeimportance: 5为主线结合 原型继承章节 的完整理论体系逐步推演alert输出的三个值并深入剖析属性查找穿越原型链delete 只作用于自身属性以及写操作不经过原型三条核心规则。读完本文你将能准确预判任意对象在get、set、delete三种操作下的行为并规避原型共享状态导致的经典陷阱。任务速览Working with prototype任务位于 1-property-after-delete/task.md完整代码如下先创建一对对象animal与rabbit随后对它们进行删除操作要求在过程中回答三个alert分别显示什么值let animal { jumps: null }; let rabbit { __proto__: animal, jumps: true }; alert( rabbit.jumps ); // ? (1) delete rabbit.jumps; alert( rabbit.jumps ); // ? (2) delete animal.jumps; alert( rabbit.jumps ); // ? (3)官方标准答案见 solution.mdtrue——来自rabbit自身null——来自原型animalundefined——整个原型链上已不存在该属性。要理解这三次输出为何如此需要先掌握属性查找在原型链上的完整算法。前置知识属性读取如何穿越原型链在 JavaScript 中每个对象都有一个隐藏的内部属性[[Prototype]]规范命名它要么是null要么引用另一个对象——这个被引用的对象称为原型。读取对象属性时如果对象自身没有该属性JavaScript 会自动沿[[Prototype]]向上查找这一机制即原型继承。设置原型最直观的方式是__proto__它是[[Prototype]]的历史 getter/setter现代规范更推荐Object.getPrototypeOf/Object.setPrototypeOf。下述代码建立了与任务相同的继承关系let animal { jumps: null }; let rabbit { __proto__: animal, // 设置 rabbit.[[Prototype]] animal jumps: true };当执行rabbit.jumps时引擎先检查rabbit自身存在jumps: true直接返回true。如果自身不存在则沿链向上先查animal再查animal的原型Object.prototype最后到达null终止整条链都找不到时返回undefined。这种自底向上的查找过程如下图所示图中的animal/rabbit即本任务使用的对象名链条还可以更长rabbit继承animallongEar再继承rabbit那么longEar.walk()会逐级向上找到animal中的方法详见 article.md。这就是查找算法的本质读取属性时从对象自身开始沿[[Prototype]]链逐级向上直到null。逐步推演三次 alert 背后的引擎行为第 1 次输出true——自身属性优先执行alert( rabbit.jumps )时rabbit拥有自己的属性jumps: true字面量中显式声明。查找算法第一步就命中不会继续访问原型因此输出true。这条规则可以用同一章节的另一个任务来印证在 2-search-algorithm/task.md 中pockets.pen沿pockets → bed → table → head链找到table中的3而table.money因为链上任何对象都没有money最终返回undefined——查找过程完全相同只是链条更长。第 2 次输出null——delete 只删自身属性原型属性随之暴露执行delete rabbit.jumps后rabbit自身的jumps: true被移除。此时rabbit不再拥有jumps查找算法只能继续向上在原型animal中找到jumps: null因此输出null。这是本题最关键的考点delete操作符只会删除对象自身的属性永远不会影响原型上的属性。delete rabbit.jumps的语义是删除rabbit这个对象自己的jumps属性而不是让原型链上再也读不到jumps。删除自身属性后被遮蔽shadowing的原型属性便重新可见。第 3 次输出undefined——整条链上已不存在继续执行delete animal.jumps此时animal自身的jumps: null也被删除。至此rabbit自身没有jumps原型animal也没有jumps再向上是Object.prototype不含jumps最终到达null终止——整条链上都找不到该属性按照查找算法返回undefined。注意此时的完整链路为rabbit → animal → Object.prototype → nullanimal是对象字面量默认继承Object.prototypeundefined正是属性不存在的规范信号查找算法走完链仍一无所获时返回它而不是报错。深入原理为什么写与删除不经过原型题目现象背后是一条贯穿整个章节的根本规则article.md 的 Writing doesnt use prototype 一节原型只参与属性读取。写入和删除操作直接作用于对象本身。也就是说delete rabbit.jumps和rabbit.jumps ...这类操作只会在rabbit对象上增删自身属性绝不会去改写animal中的同名属性。文章中的经典示例进一步说明了这一点let animal { eats: true, walk() { /* rabbit 不会用到这个方法 */ } }; let rabbit { __proto__: animal }; rabbit.walk function() { alert(Rabbit! Bounce-bounce!); }; rabbit.walk(); // Rabbit! Bounce-bounce! —— 直接在自身找到不再走原型从这以后rabbit.walk()立刻在自身命中原型中的walk形同虚设——这与题目中delete rabbit.jumps之后null重新可见是同一规则的一体两面自身属性始终优先原型属性只是后备。例外访问器属性accessor会触发 setter规则有一个例外如果属性是访问器属性get/set定义那么赋值由原型中的 setter 函数处理此时写入本质上是一次函数调用而不是简单的属性存储。article.md中的admin.fullName Alice Cooper示例即如此fullName的 setter 在原型user中但this指向调用者admin因此状态只写入adminuser不受影响。同源任务印证this 永远指向点号前的对象理解了查找与执行是两个阶段就能回答章节内另一个 importance: 5 的任务 3-proto-and-this/task.mdlet animal { eat() { this.full true; } }; let rabbit { __proto__: animal }; rabbit.eat(); // 谁被写入了 full 属性答案是rabbit。因为rabbit.eat虽然在原型animal中被找到但方法执行时this是点号前的对象rabbit官方解答见 3-proto-and-this/solution.md。结合本文主线可以串起完整的认知模型读取属性自身 → 原型链逐级向上写入/删除只操作调用对象自身除非触发 setter执行方法无论方法来自哪里this永远是调用时点号前的对象。这一特性意义重大子对象继承父对象的大量方法后各自运行继承方法时只修改自己的状态而不会污染方法仓库本身。拓展多级原型链与查找性能把查找算法应用到更长的链条上2-search-algorithm/task.mdlet head { glasses: 1 }; let table { pen: 3, __proto__: head }; let bed { sheet: 1, pillow: 2, __proto__: table }; let pockets { money: 2000, __proto__: bed }; alert( pockets.pen ); // 3在 table 中找到 alert( bed.glasses ); // 1在 head 中找到 alert( table.money ); // undefined链上不存在关于性能问题——pockets.glasses和head.glasses哪个更快官方解答2-search-algorithm/solution.md给出明确结论在现代引擎中从对象自身还是从原型获取属性性能上没有差别。引擎会记住上次找到该属性的位置并在下次直接复用该缓存且会智能地在对象结构变化时更新缓存因此这种优化是安全的。这也意味着本文讨论的沿链查找更多是语义层面的抽象模型而非逐级遍历的实现细节。避坑不要在原型上放置可变共享状态原型链共享方法很安全但共享可变数据会埋下隐患。章节最后一个任务 4-hamster-proto/task.md 演示了这个经典陷阱let hamster { stomach: [], eat(food) { this.stomach.push(food); } }; let speedy { __proto__: hamster }; let lazy { __proto__: hamster }; speedy.eat(apple); alert( speedy.stomach ); // apple alert( lazy.stomach ); // apple —— 为什么 lazy 也吃饱了原因详见 4-hamster-proto/solution.mdspeedy.eat()在原型中找到方法并以this speedy执行但this.stomach.push(...)中读取stomach时speedy自身没有该属性于是沿链找到hamster.stomach这个共享的数组引用push直接把食物塞进了原型的数组——两只仓鼠共用同一个胃。两种修复思路改用赋值而非pushthis.stomach [food]是写操作直接写入this自身不触发链上查找为每个实例声明自己的stomach在speedy、lazy上各自写stomach: []。由此引出一条通用实践描述对象自身状态的属性如stomach应写入该对象本身避免在原型上共享可变引用类型。这与本文写操作不经过原型的规则互为表里——正因为写入只作用于自身共享引用才会成为唯一需要警惕的例外。小结三条规则一次记住操作行为对应本任务现象读取属性自身 → 原型链逐级向上直到null找不到返回undefined第 1 次输出true自身命中删除属性只删除对象自身属性不影响原型第 2 次输出null原型属性重新可见删除原型属性使整条链失去该属性读取变为undefined第 3 次输出undefined执行继承方法this始终是点号前的调用对象与 3-proto-and-this 一致写/删共享引用不创建自身属性时可能改动原型上的共享数据与 4-hamster-proto 一致一句话总结本题原型只决定读的结果delete与赋值只决定自身的属性集合——把这两层分开任何get/set/delete组合的输出都可以精确预判。相关完整内容可继续阅读 原型继承章节 及同目录下的 搜索算法、写入位置、仓鼠问题 等系列任务。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐JavaScript 原型继承实战delete 操作后属性如何沿原型链查找JavaScript 原型继承实战delete 操作后属性如何沿原型链查找 导读 在 JavaScript 的原型继承中读属性走原型链、写属性只作用于对象文档/教程前端IronClaw 深度指南以隐私、安全与可扩展性为核心的 Agent OS 架构与实践IronClaw 深度指南以隐私、安全与可扩展性为核心的 Agent OS 架构与实践 导读 IronClaw 是一个以 Rust 实现的个人 AI 助手运行人工智能AI 应用交互助手AI AgentJavaScript 原型链属性查找算法实战pockets → bed → table → head 链路构建与性能基准JavaScript 原型链属性查找算法实战pockets → bed → table → head 链路构建与性能基准 导读 本文围绕 1 js/08 pr文档/教程前端上一篇mise sync 深度解析将 nvm、pyenv、uv、Homebrew 等版本管理器中的工具同步进 mise下一篇openai-agents-python 语音流水线追踪指南用 VoicePipelineConfig 配置语音 Agent 的 Trace 与敏感数据控制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表