原型继承
原型继承(Prototypal inheritance) 这个语言特性能够帮助我们实现这一需求。
[[Prototype]]
- 在 JavaScript 中,对象有一个特殊的隐藏属性
[[Prototype]]
(如规范中所命名的),它要么为null
,要么就是对另一个对象的引用。该对象被称为“原型”: - 从
object
中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这被称为“原型继承”。 - 属性
[[Prototype]]
是内部的而且是隐藏的,但是这儿有很多设置它的方式。
JavaScriptlet animal = { eats: true }; let rabbit = { jumps: true }; rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal //“`animal` 是 `rabbit` 的原型”,或者说 “`rabbit` 的原型是从 `animal` 继承而来的”。
- 在 JavaScript 中,对象有一个特殊的隐藏属性
1. `__proto__` 是 `[[Prototype]]` 的因历史原因而留下来的 getter/setter
2. `__proto__` 与内部的 `[[Prototype]]` **不一样**。`__proto__` 是 `[[Prototype]]` 的 getter/setter。
2. **写入不使用原型**
1. 原型仅用于读取属性。对于写入/删除操作可以直接在对象上进行。
```JavaScript
let animal = {
eats: true,
walk() {
/* rabbit 不会使用此方法 */
}
};
let rabbit = {
__proto__: animal
};
rabbit.walk = function() {
alert("Rabbit! Bounce-bounce!");
};
rabbit.walk(); // Rabbit! Bounce-bounce!
- “this” 的值
- 无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,
this
始终是点符号.
前面的对象。
- 无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,
- for…in 循环
for..in
循环也会迭代继承的属性。
JavaScript
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Object.keys 只返回自己的 key
alert(Object.keys(rabbit)); // jumps
// for..in 会遍历自己以及继承的键
for(let prop in rabbit) alert(prop); // jumps,然后是 eats
F.prototype
如果 F.prototype
是一个对象,那么 new
操作符会使用它为新对象设置 [[Prototype]]
。
- JavaScript 从一开始就有了原型继承。这是 JavaScript 编程语言的核心特性之一。
- 默认的 F.prototype,构造器属性
- 每个函数都有
"prototype"
属性,即使我们没有提供它。 - 默认的
"prototype"
是一个只有属性constructor
的对象,属性constructor
指向函数自身。 - ……JavaScript 自身并不能确保正确的
"constructor"
函数值。
- 每个函数都有
原生的原型
"prototype" 属性在 JavaScript 自身的核心部分中被广泛地应用。所有的内建构造函数都用到了它。
- Object.prototype
- let obj = {}; alert( obj ); // "[object Object]" ?
obj = {}
和obj = new Object()
是一个意思,其中Object
就是一个内建的对象构造函数,其自身的prototype
指向一个带有toString
和其他方法的一个巨大的对象。
- 其他内建原型
- 其他内建对象,像
Array
、Date
、Function
及其他,都在 prototype 上挂载了方法。
- 其他内建对象,像
- 基本数据类型
- 最复杂的事情发生在字符串、数字和布尔值上。
- 值
null
和undefined
没有对象包装器
- 更改原生原型
- 重要:
- 原型是全局的,所以很容易造成冲突。如果有两个库都添加了
String.prototype.show
方法,那么其中的一个方法将被另一个覆盖。 - 在现代编程中,只有一种情况下允许修改原生原型。那就是 polyfilling。
- Polyfilling 是一个术语,表示某个方法在 JavaScript 规范中已存在,但是特定的 JavaScript 引擎尚不支持该方法,那么我们可以通过手动实现它,并用以填充内建原型。
- 原型是全局的,所以很容易造成冲突。如果有两个库都添加了
- 重要:
- 从原型中借用
- 那是指我们从一个对象获取一个方法,并将其复制到另一个对象。一些原生原型的方法通常会被借用。
原型方法,没有 proto 的对象
- Object.getPrototypeOf(obj) —— 返回对象
obj
的[[Prototype]]
。 - Object.setPrototypeOf(obj, proto) —— 将对象
obj
的[[Prototype]]
设置为proto
。 - Object.create(proto, [descriptors]) —— 利用给定的
proto
作为[[Prototype]]
和可选的属性描述来创建一个空对象。
- Object.create(proto, [descriptors]) —— 利用给定的
- 原型简史
- 构造函数的
"prototype"
属性自古以来就起作用。这是使用给定原型创建对象的最古老的方式。
- 构造函数的
- 之后,在 2012 年,
Object.create
出现在标准中。它提供了使用给定原型创建对象的能力。但没有提供 get/set 它的能力。一些浏览器实现了非标准的__proto__
访问器,以为开发者提供更多的灵活性。
- 之后,在 2012 年,
- 之后,在 2015 年,
Object.setPrototypeOf
和Object.getPrototypeOf
被加入到标准中,执行与__proto__
相同的功能。由于__proto__
实际上已经在所有地方都得到了实现,但它已过时,所以被加入到该标准的附件 B 中,即:在非浏览器环境下,它的支持是可选的。
- 之后,在 2015 年,
- 之后,在 2022 年,官方允许在对象字面量
{...}
中使用__proto__
(从附录 B 中移出来了),但不能用作 getter/setterobj.__proto__
(仍在附录 B 中)。
- 之后,在 2022 年,官方允许在对象字面量
- 如果速度很重要,就请不要修改已存在的对象的
[[Prototype]]
- "Very plain" objects
- 对象可以用作关联数组(associative arrays)来存储键/值对。
- 设置和访问原型的现代方法有 - - Object.getPrototypeOf(obj) —— 返回对象
obj
的[[Prototype]]
(与__proto__
的 getter 相同)。- Object.setPrototypeOf(obj, proto) —— 将对象
obj
的[[Prototype]]
设置为proto
(与__proto__
的 setter 相同)。 - 不推荐使用内建的
__proto__
getter/setter 获取/设置原型,它现在在 ECMA 规范的附录 B 中。 - 我们还介绍了使用
Object.create(null)
或{__proto__: null}
创建的无原型的对象。
- Object.setPrototypeOf(obj, proto) —— 将对象