学习链接
原型和原型链
在 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 // truerabbit.eats = false;animal.eats === true // truerabbit.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 + Cooperuser.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"

