学习链接
原型和原型链
在 JavaScript 中,每个对象都有一个特殊的内部属性 [[Prototype]]
,它要么为 null
,要么就是对另一个对象的引用。该对象被称为 “原型”。(prototype)
也就是说,通过 [[Prototype]]
引用的对象被称为 “原型”,所有对象都有自己的原型对象。
一方面,任何一个对象都可充当其他对象的原型;另一方面,由于原型也是对象,所以它也有自己的原型。
因此,就会形成一个 “原型链”。(prototype chain)
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回 undefined
。
如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
读取和设置原型
属性 [[Prototype]]
是内部的而且是隐藏的,但是这有其他的设置它的方式。
__proto__
可使用 obj.__proto__
来设置或读取对象的原型。
但这种方法被认为已经过时且不推荐使用(deprecated)了。
(已经被移至 JavaScript 规范的附录 B,意味着仅适用于浏览器)
现代的获取/设置原形的方法
Object.getPrototypeOf(obj)
:返回对象obj
的[[Prototype]]
。Object.setPrototypeOf(obj, proto)
:将对象obj
的[[Prototype]]
设置为proto
。Object.create(proto, [descriptors])
:利用给定的proto
作为[[Prototype]]
和可选的属性描述来创建一个空对象。
注意
- 引用不能形成闭环。如果我们试图在一个闭环中分配
[[Prototype]]
,JavaScript 会抛出错误。 - 一个对象只能有一个
[[Prototype]]
。一个对象不能从两个对象同时获得继承。 [[Prototype]]
的值可以是对象,也可以是**null**
。- 即上面的方法中用来作为原型的
proto
只能为对象或者null
,否则会报错。Object.create('123')
// TypeError: Object prototype may only be an Object or null
- 即上面的方法中用来作为原型的
__proto__
的值也只能设置为 对象或null
,其他类型的值会被忽略,不会报错const obj = Object.create({a: 1});
Object.getPrototypeOf(obj) // {a: 1}
obj.__proto__ = {b: 5};
Object.getPrototypeOf(obj) // {b: 5}
obj.__proto__ = 1;
Object.getPrototypeOf(obj) // {b: 5}
__proto__
与内部的 [[Prototype]]
不一样。
**__proto__**
是 **[[Prototype]]**
的因历史原因而留下来的 getter/setter。
写入属性不使用原型
原型仅用于读取属性。
对于写入/删除操作可以直接在对象上进行,不使用原型。
let animal = { eats: true };
let rabbit = Object.create(animal);
rabbit.eats === true // true
rabbit.eats = false;
animal.eats === true // true
rabbit.eats === false // true
访问器属性
访问器(accessor)属性是一个例外,因为分配(assignment)操作是由 setter 函数处理的。因此,写入此类属性实际上与调用函数相同。
const user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} + ${this.surname}`; // +
}
};
const admin = {
__proto__: user,
isAdmin: true
};
admin.fullName // John + Smith (*)
// setter triggers!
admin.fullName = "Alice Cooper"; // (**)
admin // { isAdmin: true, name: 'Alice', surname: 'Cooper' }
admin.fullName // Alice + Cooper
user.fullName // John + Smith
在 (*)
行中,属性 admin.fullName
在原型 user
中有一个 getter,因此它会被调用。
在 (**)
行中,属性在原型中有一个 setter,因此它会被调用。
函数的 prototype
对象
F.prototype
属性仅在 new F
被调用时使用,它为新对象的 [[Prototype]]
属性赋值。
如果在创建之后,F.prototype
属性有了变化(F.prototype = <another object>
),
那么通过 new F
创建的新对象也将随之拥有新的对象作为 [[Prototype]]
,但已经存在的对象将保持旧有的值。
new
:
- 先创建一个新对象
- 新对象内部的
[[Prototype]]
指针被赋值为构造函数的prototype
属性; - 构造函数内部的 this 指向这个新对象
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回一个对象(非
null
),则返回该对象;
否则,返回刚创建的新对象
F.prototype
属性(不要把它与[[Prototype]]
弄混了)在new F
被调用时为新对象的[[Prototype]]
赋值。F.prototype
的值要么是一个对象,要么就是null
:其他值都不起作用。"prototype"
属性仅当设置在一个构造函数上,并通过new
调用时,才具有这种特殊的影响。
在常规对象上,prototype
没什么特别的:
let user = {
name: "John",
prototype: "Bla-bla" // 这里只是普通的属性
};
默认情况下,所有函数都有 F.prototype = {constructor:F}
,所以我们可以通过访问它的 "constructor"
属性来获取一个对象的构造器。
Object
和 Function
typeof Object // "function"
typeof Function // "function"
typeof Function.prototype // "function"
typeof Array // "function"