原型继承
原型继承(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,然后是 eatsF.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) —— 将对象
