整理自现代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
--------------------------------
*/