ARTICLE DETAIL

资讯详情

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

Modern JavaScript Tutorial 深度解析:构造函数 `F.prototype`、`new` 与 `constructor` 属性的完整指南

Modern JavaScript Tutorial 深度解析:构造函数 `F.prototype`、`new` 与 `constructor` 属性的完整指南 文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载导读在 JavaScript 中通过构造函数new F()创建对象时F.prototype属性决定了新对象的内部[[Prototype]]指向——这是语言中最古老、使用最广泛的设置原型的方式也是理解后续class语法与原型链机制的地基。本指南以 Modern JavaScript Tutorial 的 F.prototype 章节 为主体结合同章节配套习题与 构造函数与 new、原型继承 等仓库章节系统讲解F.prototype的工作时机、默认constructor属性的行为、替换原型带来的陷阱与正确修复方式并给出可直接运行的验证代码。读完本文你将能准确回答new是如何借助prototype设置继承关系的为什么覆盖Rabbit.prototype后rabbit.constructor会失效以及如何安全地利用constructor创建同类型新对象等问题。一、从[[Prototype]]说起构造函数如何参与原型继承在 原型继承章节 中我们已知每个 JavaScript 对象都有一个隐藏的内部属性[[Prototype]]它要么是null要么指向另一个对象称为原型。当读取对象上不存在的属性时JavaScript 会自动沿[[Prototype]]链向上查找这就是原型式继承。而通过构造函数创建对象时new操作符恰好会利用构造函数上的一个普通属性prototype来为新对象指定[[Prototype]]。也就是说如果F.prototype是一个对象那么new F()创建的新对象其[[Prototype]]会被设置为F.prototype所指向的对象。这里的F.prototype并不是什么特殊语法它就是挂在构造函数F上的一个名为prototype的普通属性——注意不要与内部属性[[Prototype]]混淆二者是完全不同的东西。基础示例让所有兔子继承动物let animal { eats: true }; function Rabbit(name) { this.name name; } Rabbit.prototype animal; // 关键赋值 let rabbit new Rabbit(White Rabbit); // rabbit.__proto__ animal alert( rabbit.eats ); // trueRabbit.prototype animal这句话的字面含义是当创建一个new Rabbit时把它的[[Prototype]]赋值为animal。于是rabbit自身没有eats属性但可以通过原型链从animal上读到它图中水平箭头表示prototype是一个普通属性垂直箭头表示rabbit对animal的继承关系即[[Prototype]]引用。这正是理解本节的关键心智模型构造函数通过一个普通属性把继承关系注入到每次new创建的新对象上。历史背景为什么老代码大量依赖它值得强调的是原型式继承从 JavaScript 诞生起就是语言核心特性之一。但在早期开发者并没有直接访问[[Prototype]]的途径唯一可靠的做法就是本文描述的——构造函数上的prototype属性。因此直到今天仍有大量现存脚本沿用这一模式。这正是我们有必要透彻理解它的原因。现代的Object.create、Object.setPrototypeOf等替代方案在 原型方法章节 中有专门讲解。关键时机F.prototype只在new F()调用的那一刻起作用F.prototype属性仅在调用new F时被读取用来为新对象设置[[Prototype]]。它不是一个持续生效的引用function Rabbit() {} Rabbit.prototype { eats: true }; let rabbit1 new Rabbit(); // 此时 rabbit1 的 [[Prototype]] 是 { eats: true } Rabbit.prototype { eats: false, jumps: true }; // 之后才改写 prototype let rabbit2 new Rabbit(); // rabbit2 的 [[Prototype]] 是 { eats: false, jumps: true } alert(rabbit1.eats); // true —— 旧对象保留旧的 [[Prototype]] alert(rabbit2.eats); // false —— 新对象获得新的 [[Prototype]]也就是说已创建的对象不受后续F.prototype改写的任何影响只有之后新建的对象会使用新值。这一点在后面的习题验证中还会反复出现。二、默认的F.prototype与constructor属性每个函数包括普通函数都天然拥有prototype属性即使我们完全没有提供它。默认的prototype是一个只含一个属性constructor的对象且constructor指回函数自身function Rabbit() {} /* 默认 prototype 相当于 Rabbit.prototype { constructor: Rabbit }; */可以直接验证function Rabbit() {} // 默认情况下 // Rabbit.prototype { constructor: Rabbit } alert( Rabbit.prototype.constructor Rabbit ); // true如果我们什么都不做那么所有通过new Rabbit()创建的对象都会通过[[Prototype]]继承到这个constructor属性function Rabbit() {} // 默认情况下 // Rabbit.prototype { constructor: Rabbit } let rabbit new Rabbit(); // 继承自 {constructor: Rabbit} alert(rabbit.constructor Rabbit); // true来自原型利用constructor创建同类型对象constructor属性的实用价值在于当我们手里有一个对象、却不知道它是用哪个构造函数创建时例如来自第三方库可以通过obj.constructor再造一个同类对象function Rabbit(name) { this.name name; alert(name); } let rabbit new Rabbit(White Rabbit); let rabbit2 new rabbit.constructor(Black Rabbit); // 等价于 new Rabbit(Black Rabbit)这正是 配套习题 Create an object with the same constructor 探讨的场景new obj.constructor()是否可靠完全取决于constructor属性是否仍指向正确的构造函数。只要没有覆盖过默认prototype它就必然可靠function User(name) { this.name name; } let user new User(John); let user2 new user.constructor(Pete); alert( user2.name ); // Pete成功它之所以成功是因为User.prototype.constructor User见 该习题解答。三、最大的坑JavaScript 并不保证constructor始终正确JavaScript 自身并不保证constructor值始终正确。它只是默认出现在函数的prototype里仅此而已。之后这个属性会变成什么样完全取决于我们自己。尤其是如果整体替换掉默认的prototype新的 prototype 对象里就不会再有constructor了。例如function Rabbit() {} Rabbit.prototype { jumps: true }; let rabbit new Rabbit(); alert(rabbit.constructor Rabbit); // false此时rabbit沿原型链查到的constructor是更上层Object.prototype.constructor即内置的Object而不是Rabbit。习题 Changing prototype 与解答 solution.md 里new obj.constructor()失效的完整链路正是这样的在user自身找constructor——没有沿原型链到User.prototype——也没有因为被User.prototype {}覆盖时忘记补上再往上到内置Object.prototype——这里存在Object.prototype.constructor Object最终执行的是new Object(Pete)name自然成了undefined。也就是说丢失constructor的后果是同类型对象的复制静默地变成了普通Object创建代码不报错但结果完全错误。四、两种保住constructor的正确姿势既然整块替换会丢失constructor官方教程给出了两个修复方案方案一不要整体覆盖只增删默认 prototype 的属性保留默认prototype对象其中含constructor只往上面添加/删除属性function Rabbit() {} // 不要整体覆盖 Rabbit.prototype // 只往上面添加属性 Rabbit.prototype.jumps true // 默认的 Rabbit.prototype.constructor 得以保留方案二整体覆盖时手动补回constructor如果确实需要用一个全新的对象替换prototype记得把constructor一并写进去Rabbit.prototype { jumps: true, constructor: Rabbit }; // 因为手动补上了 constructor现在它也正确两种方式都能保证rabbit.constructor Rabbit从而让new obj.constructor()这类代码继续可靠工作。五、配套习题实战改写prototype的四种情形验证仓库中 1-changing-prototype 习题从四个角度测试对F.prototype语义的理解其完整解答见 solution.md。初始代码如下function Rabbit() {} Rabbit.prototype { eats: true }; let rabbit new Rabbit(); alert( rabbit.eats ); // true情形 1整体改写Rabbit.prototype {}function Rabbit() {} Rabbit.prototype { eats: true }; let rabbit new Rabbit(); Rabbit.prototype {}; // 改写发生在 new 之后 alert( rabbit.eats ); // 答案true。Rabbit.prototype的赋值只对之后新建的对象设置[[Prototype]]对已存在的rabbit毫无影响——它的[[Prototype]]仍是旧对象{ eats: true }。情形 2修改Rabbit.prototype.eats falsefunction Rabbit() {} Rabbit.prototype { eats: true }; let rabbit new Rabbit(); Rabbit.prototype.eats false; alert( rabbit.eats ); // 答案false。对象是按引用赋值的。Rabbit.prototype指向的对象并没有被复制rabbit的[[Prototype]]与Rabbit.prototype引用的是同一个对象。因此通过任一引用修改其内容另一引用都会看到变化。情形 3delete rabbit.eatsfunction Rabbit() {} Rabbit.prototype { eats: true }; let rabbit new Rabbit(); delete rabbit.eats; alert( rabbit.eats ); // 答案true。所有delete操作都直接作用于对象自身。rabbit自己没有eats属性它只在原型上所以删除操作不产生任何效果属性仍可从原型读到。这正是 原型继承章节 强调的写/删操作不走原型原则。情形 4delete Rabbit.prototype.eatsfunction Rabbit() {} Rabbit.prototype { eats: true }; let rabbit new Rabbit(); delete Rabbit.prototype.eats; alert( rabbit.eats ); // 答案undefined。这次删除的是原型对象上的eats属性它从整个原型链中消失因此rabbit.eats再也查不到返回undefined。这四个情形共同印证了第三节的核心结论F.prototype只在new的瞬间被消费一次对象内容按引用共享读写删操作只在对象自身生效。它们是排查原型相关 bug 时最有价值的判断依据。六、总结三条必须记住的规则本节内容看似简单但包含几条极易踩坑的要点官方教程的 Summary 将其凝练为F.prototype不要与[[Prototype]]混淆会在new F()被调用时为新对象的[[Prototype]]赋值。它只在构造函数配合new使用时才有这种特殊效果。F.prototype的值必须是对象或null其他值不会生效。这也与原型继承章节中[[Prototype]]只能是对象或null的约束一脉相承。默认情况下所有函数都有F.prototype { constructor: F }因此可以通过对象的constructor属性拿到它的构造函数。但一旦整体覆盖prototype而不补回constructor这个能力就会静默丢失。最后还要强调一个易混淆点在普通对象上prototype毫无特殊之处它只是一个普通属性名let user { name: John, prototype: Bla-bla // 没有任何魔法 };只有挂在构造函数上、并经由new调用时prototype才承担起设置[[Prototype]]的职责。七、延伸阅读构造函数与 operator new理解new的完整执行流程创建空对象、绑定this、返回this与本节的直接前置知识。原型继承Prototypal inheritance[[Prototype]]、原型链查找与this语义的完整介绍。原型方法没有proto的对象现代替代方案Object.getPrototypeOf / setPrototypeOf / create与原型历史的演进脉络。原生原型Native prototypes内置对象如Object.prototype的constructor如何在实践中影响上面的查找链路。配套习题与解答Changing prototype、Create an object with the same constructor。理解F.prototype之后后续学习 class 语法 与更高级的继承模式会顺畅得多——class正是建立在这套原型机制之上的语法糖。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐用 new obj.constructor() 克隆同构造器对象Modern JavaScript Tutorial 的 F.prototype 与 constructor 属性详解用 new obj.constructor 克隆同构造器对象Modern JavaScript Tutorial 的 F.prototype 与 constr文档/教程前端wasm-bindgen 指南用 constructor 属性将 JavaScript 构造函数new 运算符绑定到 Rustwasm bindgen 指南用 constructor 属性将 JavaScript 构造函数 new 运算符绑定到 Rust wasm_bindgen开发工具Modern JavaScript Tutorial深入解析 new Function 动态构造函数的语法、闭包边界与压缩器安全性Modern JavaScript Tutorial深入解析 new Function 动态构造函数的语法、闭包边界与压缩器安全性 new Function文档/教程前端上一篇wxBot代码规范与最佳实践编写高质量机器人代码的完整指南下一篇MLKit Android机器学习工具包终极集成与最佳实践指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表