整理自现代Javascript、MDN
原型与继承
对象有一个特殊的隐藏属性
[[Prototype]]
(如规范中所命名的),它要么为null
,要么就是对另一个对象的引用。该对象被称为“原型”每个实例对象(object)都有一个私有属性(称之为
__proto__
)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(_proto_),层层向上直到一个对象的原型对象为null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节几乎所有 JavaScript 中的对象都是位于原型链顶端的
Object
的实例当我们从
object
中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这种行为被称为“原型继承”
原型链
当访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
[[Prototype]]
属性
[[Prototype]]
是内部的而且是隐藏的。但也有很多设置它的方式
-
使用特殊的名字
__proto__
__proto__
是[[Prototype]]
的因历史原因而留下来的 getter/setter,现代编程语言建议我们应该使用函数**Object.getPrototypeOf/Object.setPrototypeOf
** 来取代__proto__
去 get/set 原型使用规则
- 引用不能形成闭环。如果我们试图在一个闭环中分配
__proto__
,JavaScript 会抛出错误 __proto__
的值可以是对象,也可以是null
。而其他的类型都会被忽略
let animal = { eats: true, walk() { alert("Animal walk"); } }; let rabbit = { jumps: true }; rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal // 现在这两个属性我们都能在 rabbit 中找到: alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true // walk 方法是从原型中获得的 rabbit.walk(); // Animal walk /** * 当 `alert` 试图读取 `rabbit.eats` `(**)` 时,因为它不存在于 `rabbit` 中,所以 JavaScript 会顺着 `[[Prototype]]` 引用,在 `animal` 中查找(自下而上) */ // 原型链可以很长 let longEar = { earLength: 10, __proto__: rabbit }; // walk 是通过原型链获得的 longEar.walk(); // Animal walk alert(longEar.jumps); // true(从 rabbit) /** ---------------------------- animal: eats: true walk: function ---------------------------- ^ | [[Prototype]] | ---------------------------- rabbit: jumps: true ---------------------------- ^ | [[Prototype]] | ---------------------------- longEar: earLength: 10 ---------------------------- */ 复制代码
- 引用不能形成闭环。如果我们试图在一个闭环中分配
写入不使用原型
原型仅用于读取属性。对于写入/删除操作可以直接在对象上进行
let animal = {
eats: true,
walk() {
/* rabbit 不会使用此方法 */
}
};
let rabbit = {
__proto__: animal
};
rabbit.walk = function() {
alert("Rabbit! Bounce-bounce!");
};
// rabbit.walk() 将立即在对象中找到该方法并执行,而无需使用原型
rabbit.walk(); // Rabbit! Bounce-bounce!
/**
----------------------------
animal:
eats: true
walk: function
----------------------------
^
| [[Prototype]]
|
----------------------------
rabbit:
walk: function
----------------------------
*/
复制代码
this
的值
无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,
this
始终是点符号.
前面的对象方法是共享的,但对象状态不是
// animal 有一些方法
let animal = {
walk() {
if (!this.isSleeping) {
alert(`I walk`);
}
},
sleep() {
this.isSleeping = true;
}
};
let rabbit = {
name: "White Rabbit",
__proto__: animal
};
// 修改 rabbit.isSleeping
rabbit.sleep();
alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined(原型中没有此属性)
/**
----------------------------
animal:
walk: function
sleep: function
----------------------------
^
| [[Prototype]]
|
----------------------------
rabbit:
name: "White Rabbit"
----------------------------
*/
复制代码
for...in
循环
for..in
循环也会迭代继承的、可枚举的属性所有其他的键/值获取方法仅对对象本身起作用,忽略继承的属性
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
复制代码
- 使用
obj.hasOwnProperty(key)
过滤继承属性
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
alert(`Our: ${prop}`); // Our: jumps
} else {
alert(`Inherited: ${prop}`); // Inherited: eats
}
}
/**
--------------------------------
null
--------------------------------
^
| [[Prototype]]
|
--------------------------------
Object.prototype:
toString: function
hasOwnProperty: function
...
--------------------------------
^
| [[Prototype]]
|
--------------------------------
animal:
eats: true
--------------------------------
^
| [[Prototype]] // rabbit.__proto__ ==== animal
|
--------------------------------
rabbit:
jumps: true
--------------------------------
*/
复制代码
F.prototype
这里的
F.prototype
指的是F
的一个名为"prototype"
的常规属性(普通构造函数的prototype
)
F.prototype
属性(不要把它与[[Prototype]]
弄混了)在new F
被调用时为新对象的[[Prototype]]
赋值F.prototype
的值要么是一个对象,要么就是null
:其他值都不起作用"prototype"
属性仅在设置了一个构造函数(constructor function),并通过new
调用时,才具有这种特殊的影响
-
如果 F.prototype 是一个对象,那么 new 操作符会使用它为新对象设置 [[Prototype]]
let animal = { eats: true }; function Rabbit(name) { this.name = name; } // 设置 Rabbit.prototype = animal 的字面意思是:“当创建了一个 new Rabbit 时,把它的 [[Prototype]] 赋值为 animal” Rabbit.prototype = animal; let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true /** ------------------------ prototype -------------------------- Rabbit -----------> animal: eats: true ------------------------ --------------------------- ^ | | [[Prototype]] | ---------------------------- rabbit: name: "White Rabbit" ---------------------------- */ 复制代码
-
默认的
F.prototype
,构造器属性每个函数都有
"prototype"
属性,即使我们没有提供它。默认的
"prototype"
是一个只有属性constructor
的对象,属性constructor
指向函数自身function Rabbit() {} /* default prototype Rabbit.prototype = { constructor: Rabbit }; */ function Rabbit() {} // by default: // Rabbit.prototype = { constructor: Rabbit } let rabbit = new Rabbit(); // inherits from {constructor: Rabbit} alert(rabbit.constructor == Rabbit); // true (from prototype) 复制代码
-
JavaScript 自身并不能确保正确的
"constructor"
函数值constructor
存在于函数的默认"prototype"
中,如果我们将整个默认 prototype 替换掉,那么其中就不会有"constructor"
为了确保正确的
"constructor"
,我们可以选择添加/删除属性到默认"prototype"
,而不是将其整个覆盖function Rabbit() {} Rabbit.prototype = { jumps: true }; let rabbit = new Rabbit(); alert(rabbit.constructor === Rabbit); // false function Rabbit() {} // 不要将 Rabbit.prototype 整个覆盖 // 可以向其中添加内容 Rabbit.prototype.jumps = true // 默认的 Rabbit.prototype.constructor 被保留了下来 // 也可以手动重新创建 constructor 属性 Rabbit.prototype = { jumps: true, constructor: Rabbit }; // 这样的 constructor 也是正确的,因为我们手动添加了它 复制代码
修改prototype
-
修改、删除、新增构造函数中
prototype
中的属性时(在这之前prototype
对象没有被重新赋值),已被实例化的对象所继承的prototype
属性会随着变化function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); Rabbit.prototype = { eats: false }; // (*) alert( rabbit.eats ); // true Rabbit.prototype.eats = false alert( rabbit.eats ); // true (*)对 prototype 重新赋值了 复制代码
-
修改构造函数的整个
prototype
时,已被实例化的对象所继承的prototype
属性不变function Rabbit() {} Rabbit.prototype = { eats: true }; let rabbit = new Rabbit(); Rabbit.prototype.eats = false alert( rabbit.eats ); // false 复制代码
原型方法
__proto__
被认为是过时且不推荐使用的,因此推荐使用以下原型方法
- Object.create(proto, [descriptors])—— 利用给定的
proto
作为[[Prototype]]
和可选的属性描述来创建一个空对象 - Object.getPrototypeOf(obj) —— 返回对象
obj
的[[Prototype]]
- Object.setPrototypeOf(obj, proto)—— 将对象
obj
的[[Prototype]]
设置为proto
let animal = {
eats: true
};
// 创建一个以 animal 为原型的新对象
let rabbit = Object.create(animal);
alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // true
Object.setPrototypeOf(rabbit, {}); // 将 rabbit 的原型修改为 {}
// Object.create 有一个可选的第二参数:属性描述器。我们可以在此处为新对象提供额外的属性
let rabbit2 = Object.create(animal, {
jumps: {
value: true
}
});
复制代码
-
Object.create
提供了一种简单的方式来浅拷贝一个对象的所有描述符(包括可枚举的和不可枚举的)`let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); 复制代码
-
返回“自身”的方法,下面指的是
obj
对象- Object.keys(obj) / Object.values(obj) / Object.entries(obj) —— 返回一个可枚举的由自身的字符串属性名/值/键值对组成的数组
- Object.getOwnPropertySymbols(obj) —— 返回一个由自身所有的 symbol 类型的键组成的数组
- Object.getOwnPropertyNames(obj) —— 返回一个由自身所有的字符串键组成的数组
- Reflect.ownKeys(obj) —— 返回一个由自身所有键组成的数组
- obj.hasOwnProperty(key):如果
obj
拥有名为key
的自身的属性(非继承而来的),则返回true
[[Prototype]]
、__proto__
与prototype
含义
[[Prototype]]
对象的原型,本身也是一个对象。是对象的一个特殊的隐藏属性 ,它要么为
null
,要么就是对另一个对象的引用
__proto__
是
[[Prototype]]
的因历史原因而留下来的 getter/setter,也就是可以通过__proto__
去访问[[Prototype]]
,是一个访问器属性。当然,推荐使用使用**Object.getPrototypeOf/Object.setPrototypeOf
**去访问
prototype
为了方便理解,我将这里的
prototype
分为三类:分别是原生的prototype
(Object.prototype
)、内置对象的prototype
(Function
、Array
、Number
…)、普通构造函数的prototype
我通俗地理解:只要可以
new
的对象或者函数,都有prototype
属性
- 原生的
prototype
(Object.prototype
)、内置对象的prototype
“一切都从对象继承而来”。所有的内建原型顶端都是
Object.prototype
let arr = [1, 2, 3]; // 它继承自 Array.prototype alert( arr.__proto__ === Array.prototype ); // true // 接下来继承自 Object.prototype alert( arr.__proto__.__proto__ === Object.prototype ); // true // 原型链的顶端为 null alert( arr.__proto__.__proto__.__proto__ ); // null 复制代码
- 所有的内建对象都遵循相同的模式(pattern)
- 方法都存储在 prototype 中(
Array.prototype
、Object.prototype
、Date.prototype
等)- 对象本身只存储数据(数组元素、对象属性、日期)
- 原始数据类型也将方法存储在包装器对象的 prototype 中:
Number.prototype
、String.prototype
和Boolean.prototype
。只有undefined
和null
没有包装器对象
- 构造函数的
prototype
构造函数的原型对象,也称构造器属性。每个函数都有默认的
"prototype"
属性,是一个只有属性constructor
的对象,属性constructor
指向函数自身在构造函数创建实例对象时,
prototype
为实例对象的原型([[Prototype]]
)赋值。即实例对象的原型是构造函数的prototype
决定的写到这里,我就想:构造函数的原型是什么?它的原型的原型是什么?
Function
与构造函数的prototype
构造函数的
__proto__
===Function.prototype
构造函数是由
Function
实例化的,所有函数都是Function
的对象
Function.__proto__ === Function.prototype
全局的
Function
对象没有自己的属性和方法,但是,因为它本身也是一个函数,所以它也会通过原型链从自己的原型链Function.prototype
上继承一些属性和方法
function Cat(name) { this.name = name; } Cat.prototype // { constructor: Cat(name) } // Cat 原型指向 Function 的 prototype,因为所有函数都是由 Function 构造的 Cat.__proto__ === Function.prototype; // true // Function 的原型指向自身的 prototype,从自身的 prototype 中继承一些属性和方法 Function.__proto__ === Function.prototype; // true 复制代码
关系
-
[[Prototype]]
是属于对象的,是对象的一个隐藏属性,也就是“原型” -
__proto__
是[[Prototype]]
(“原型”)的getter/setter
,就是可以通过访问对象的__proto__
来获取对象的原型 -
对象是由构造函数所实例化的。
prototype
是构造函数的默认的构造属性。在实例化对象时,为实例化对象指定原型(给对象的[[Prototype]]
赋值)。当然还有Object.prototype
和内置对象的prototype
function Cat(name) {
this.name = name;
}
let cat = new Cat('Tony');
/**
Cat:构造函数;
{constructor: Cat(name)}:构造函数的 prototype;
cat:Cat 的实例化对象;
------------------------ prototype --------------------------
Cat -----------> {}:
<----------- constructor: Cat(name)
------------------------ constructor ---------------------------
^
|
| [[Prototype]](可通过 __proto__ 访问)
|
----------------------------
cat:
name: "Tony"
----------------------------
*/
// ==============================================================================
function Rabbit(name) {
this.name = name;
}
let animal = {
eats: true
};
// 设置 Rabbit.prototype = animal 的字面意思是:“当创建了一个 new Rabbit 时,把它的 [[Prototype]] 赋值为 animal”
Rabbit.prototype = animal;
let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal
alert( rabbit.eats ); // true
/**
Rabbit:构造函数;
animal:构造函数的 prototype;
rabbit:Rabbit 的实例化对象;
------------------------ prototype --------------------------
Rabbit -----------> animal:
eats: true
------------------------ ---------------------------
^
|
| [[Prototype]](可通过 __proto__ 访问)
|
----------------------------
rabbit:
name: "White Rabbit"
----------------------------
*/
// =============================================================================
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
/**
--------------------------------
null
--------------------------------
^
| [[Prototype]]
|
--------------------------------
Object.prototype:
toString: function
hasOwnProperty: function
...
--------------------------------
^
| [[Prototype]]
|
--------------------------------
animal:
eats: true
--------------------------------
^
| [[Prototype]] // rabbit.__proto__ ==== animal
|
--------------------------------
rabbit:
jumps: true
--------------------------------
*/
复制代码