讲清楚 JavaScript 原型

讲清楚 JavaScript 原型

💡 原文中文,约15900字,阅读约需38分钟。
📝

内容提要

本文讲解JavaScript原型与原型链:对象通过__proto__指向原型,函数具有prototype属性,实例的原型即构造器的prototype。访问属性时会沿原型链查找。ES6 class是构造器的语法糖,extends实现原型继承与组合继承。原型污染可能导致安全漏洞,可用Object.create(null)和Object.freeze预防。

🔎

延伸解读

__proto__ 与 prototype 的本质区别

文章强调,__proto__ 是定义在 Object.prototype 上的访问器属性,用于读写对象的内部原型 [[Prototype]];而 prototype 是函数特有的属性,指向由该函数构造的实例的原型。函数同时拥有 __proto__ 和 prototype,但二者用途不同。理解这一点可避免混淆,例如 stu.__proto__ 指向 Student.prototype,而 Student.__proto__ 指向 Function.prototype。

原型链查找与性能影响

访问对象属性时,若自身不存在,会沿原型链向上查找,直到 Object.prototype 或 null。文章指出,原型污染会延长查找路径,增加遍历次数,并可能引发意外逻辑错误。例如,污染 Object.prototype 后,for...in 循环会枚举出额外属性,即使使用 hasOwnProperty 过滤,访问不存在的属性时仍会触发原型查找,影响性能。

ES6 class 与原型继承的等价性

文章通过 Babel 编译结果说明,ES6 class 是构造器的语法糖,class 方法定义在 prototype 上。extends 实现的是原型继承与组合继承:子类原型通过 Object.create(superClass.prototype) 继承父类原型,子类构造器内调用 super 将父类实例属性组合到子类实例。这解释了 class 继承的底层机制,有助于理解原型链在继承中的作用。

预防原型污染的实用手段

文章建议使用 Object.create(null) 创建无原型对象,这样即使设置 __proto__ 也无法污染原型;或使用 Object.freeze 冻结对象,使其不可扩展,修改原型会抛出 TypeError。这些方法在类库开发中常见,如 EventEmitter3 使用 Object.create(null)。养成良好编码习惯,避免使用会修改对象原型的函数,是预防原型污染的关键。

❓

Q&A

JavaScript 中 __proto__ 和 prototype 有什么区别?

__proto__ 是对象上的非标准属性,用于访问对象的原型;prototype 是函数上的属性,指向一个对象,该对象会成为使用 new 调用该函数创建的实例的原型。即:实例的 __proto__ 等于构造函数的 prototype。

原型链是如何工作的?

当访问对象的属性时,如果对象自身没有该属性,就会沿着 __proto__ 指向的原型向上查找,直到 Object.prototype,如果还没有则返回 undefined。这条由原型组成的链就是原型链。

ES6 class 和传统的构造函数有什么关系?

ES6 class 是构造函数的语法糖。class 中定义的方法实际上定义在构造函数的 prototype 上,constructor 方法相当于构造函数本身。使用 extends 实现继承时,编译后是原型继承和组合继承的结合。

如何实现原型继承?

原型继承要求子类的原型能够访问父类原型上的属性。可以通过将子类的 prototype 设置为一个以父类 prototype 为原型的对象来实现,例如使用 Object.create(superClass.prototype),并修正 constructor 指向。

什么是原型污染?如何预防?

原型污染是指攻击者通过修改对象的原型(通常是 Object.prototype)来注入属性,可能导致逻辑漏洞或性能问题。预防措施包括使用 Object.create(null) 创建无原型对象,以及使用 Object.freeze 冻结对象防止修改。

new 运算符的原理是什么?

new 运算符执行以下步骤:1. 创建一个新对象,其原型为构造函数的 prototype;2. 将构造函数的 this 指向新对象并执行;3. 如果构造函数返回对象则返回该对象,否则返回新对象。

🏷️

标签

➡️

继续阅读