本文档解释了 JavaScript 的原型继承系统,特别是 [[Prototype]] 链如何使对象能够将属性访问委托给其他链接的对象。原型继承是 JavaScript 的三大基本支柱之一(与作用域/闭包和类型/强制转换并列),并且是 JavaScript 中 class 语法和面向对象模式的基础。有关基于此机制构建的类的更多信息,请参阅 Classes。
JavaScript 中的原型继承是指一种机制,通过一个称为 [[Prototype]] 的内部链接,对象可以从其他对象继承属性和方法。与 Java 或 C++ 等语言中的类继承不同,JavaScript 使用一种基于委托的方法,当属性在对象本身上找不到时,对象会将属性查找委托给链接的原型对象。
来源: objects-classes/ch2.md267-307 objects-classes/ch3.md10-18
当你访问对象上的属性或方法时,JavaScript 首先会在对象本身上查找该属性。如果找不到,它会沿着内部 [[Prototype]] 链接到另一个对象,并在那里继续搜索。这种对象的链接构成了所谓的“原型链”。
JavaScript 中的每个标准对象都有这个内部 [[Prototype]] 链接,默认情况下,所有对象都链接到 Object.prototype。链最终会以 null 结束。
来源: objects-classes/ch2.md267-299 objects-classes/ch3.md157-162
属性查找过程如下:
[[Prototype]] 链接到下一个对象null)undefined这种委托机制允许对象“继承”它们实际上不拥有的属性。
来源: objects-classes/ch2.md296-307 objects-classes/ch3.md656-673
JavaScript 提供了几种创建和修改原型链接的方法。
Object.create()创建具有特定原型的对象的直接方法是使用 Object.create()
Object.create() 创建一个新对象,其 [[Prototype]] 链接到作为第一个参数传递的对象。
来源: objects-classes/ch2.md344-359 objects-classes/ch5.md183-219
__proto__ 的对象字面量您还可以使用 __proto__ 属性在创建对象时设置原型。
请注意,__proto__ 是 JavaScript 引擎识别的特殊属性,但它不是核心语言规范的一部分。它出于兼容性而被支持,但通常更推荐使用 Object.create()。
来源: objects-classes/ch2.md360-373 objects-classes/ch5.md77-89
[[Prototype]] 链接您可以通过将 null 传递给 Object.create() 来创建一个没有原型的对象。
这会创建一个不继承任何属性或方法,甚至不继承 Object.prototype 的对象。此类对象有时被称为“字典对象”,在您想要一个纯数据结构而没有任何继承的包袱时很有用。
来源: objects-classes/ch2.md376-392
JavaScript 原型系统中最令人困惑的方面之一是内部 [[Prototype]] 链接与公共 .prototype 属性之间的区别。
[[Prototype]]:所有对象上都存在一个内部链接,指向另一个对象(或 null)。.prototype:一个普通的属性,仅存在于函数对象上。当函数与 new 一起用作构造函数时,新对象的 [[Prototype]] 将被设置为函数的 .prototype 属性。来源: objects-classes/ch2.md393-416 objects-classes/ch3.md127-162
原型继承与类继承有显著不同。
| 类继承 | 原型继承 |
|---|---|
| 类是蓝图 | 对象链接到其他对象 |
| 实例继承类行为的副本 | 对象将属性查找委托给原型对象 |
| 在编译时定义的静态关系 | 可在运行时修改的动态关系 |
| 深层继承结构常见 | 通常更扁平的对象关系 |
| “is-a”关系 | “delegates-to”关系 |
在类继承中,实例从它们的类继承行为的副本。在原型继承中,当需要时,对象只是将属性访问委托给其他对象。
来源: objects-classes/ch3.md659-685 objects-classes/ch5.md8-30
在讨论 JavaScript 的原型系统时,“继承”一词实际上有些误导。实际发生的情况用“行为委托”来描述更佳。
当一个对象上找不到属性时,该请求会被委托给它的原型。不发生属性或方法的复制。这是与类继承的一个重要区别,在类继承中,行为通常从类复制到实例。
来源: objects-classes/ch2.md301-307 objects-classes/ch5.md247-266
理解的一个关键概念是,原型“继承”是通过委托共享行为来实现的,而不是复制。
当你在原型对象上定义方法时,这些方法会保留在原型上,并被链接到该原型的所有对象共享。它们不会复制到每个实例,这在内存效率上更高。
来源: objects-classes/ch3.md659-708
JavaScript 中的 instanceof 运算符用于检查一个对象是否是某个构造函数的实例。其底层是通过遍历原型链来实现的。
instanceof 检查构造函数的 .prototype 属性是否存在于对象的 [[Prototype]] 链中的任何位置。
来源: objects-classes/ch3.md598-644
在使用原型继承时,理解方法和数据属性(成员)之间的区别非常重要
这种方法内存效率更高,因为所有实例都通过原型链共享相同的实现方法。
来源: objects-classes/ch3.md188-233 objects-classes/ch3.md659-708
JavaScript 中的 ES6 `class` 语法主要是构建在原型系统之上的语法糖
当你在 JavaScript 中定义一个 `class` 时
尽管 `class` 语法使 JavaScript 看起来更像传统的面向对象语言,但在底层,它仍然使用相同的原型继承机制。
来源: objects-classes/ch3.md8-18 objects-classes/ch3.md177-202
有几种方法可以检查原型链中的属性
| 操作 | 仅检查自有属性 | 检查原型链 |
|---|---|---|
obj.hasOwnProperty("prop") | ✓ | ✗ |
Object.hasOwn(obj, "prop") | ✓ | ✗ |
"prop" in obj | ✗ | ✓ |
obj.prop !== undefined | ✗ | ✓ |
`in` 操作符和直接属性访问会检查整个原型链,而 `hasOwnProperty()` 和 `Object.hasOwn()` 只检查对象的自有属性。
来源: objects-classes/ch1.md589-640 objects-classes/ch2.md307-340
与其从类和继承的角度思考,不如利用 JavaScript 的原型系统来实现一种委托式设计模式
在这种方法中
这种模式有时被称为“OLOO”(Objects Linked to Other Objects),它代表了一种比传统类层次结构更原生的 JavaScript 代码组织方式。
来源: objects-classes/ch5.md268-324 objects-classes/ch5.md332-502
原型继承是 JavaScript 中的一个基本机制,它
理解原型继承对于有效的 JavaScript 编程至关重要,即使在使用 `class` 等更高级的抽象时也是如此。
来源: objects-classes/ch2.md267-307 objects-classes/ch3.md8-18 objects-classes/ch5.md247-266