概述
JavaScript语言是动态的,本身不提供基于类语言(如Java或C++)的class实现。在ES2015/ES6中引入class关键字,只是JavaScript基于原型的语法糖。
尽管原型继承被认为是JavaScript的弱点之一,但原型继承模型本身比经典模型更强大。列如,在原型模型的基础上构建经典模型相当简单。
当谈到继承时,JavaScript只有一种结构:对象。每个实例对象(object)都有一个指向它的原型对象(prototype)的私有属性_proto_。该原型对象也有一个指向自己的原型对象(prototype)的私有属性_proto_。层层向上直到一个对象的原型对象为null。null因为没有原型,所以作为这个原型链中的最后一个环节。几乎所有JavaScript中的对象都是位于原型链顶端的Object的实例。
基于原型链的继承
继承属性
JavaScript对象是动态的属性"包"(指其自己的属性)。JavaScript对象有一个指向一个原型对象的链。当访问一个对象的属性时,首先在该对象上查找,然后在该对象的原型上查找,紧接着在该对象的原型的原型上,依次层层向上搜索,直到找到一个名字匹配的属性或到达该原型链的末尾。
注意
遵循ECMAScript标准,someObject.[[Prototype]]符号是用于指向someObject的原型。
从ECMAScript6开始,[[Prototype]]可以通过Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。
这个等同于JavaScript的非标准但许多浏览器实现的属性_proto_。
Object.prototype属性表示Object的原型对象。被构造函数创建的实例对象的[[prototype]]指向func的prototype属性。两者不要混淆。
接下来演示当尝试访问属性时会发生什么
let f = function () {
this.a = 1;
this.b = 2;
}
let o = new f(); // {a:1,b:2}
// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要在f函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c(其实就是o._proto_或者o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 这就是原型链的末尾,即 null,根据定义,null 没有[[Prototype]].
// 综上,整个原型链如下:{a:1,b:2} ---> {b:3,c:4} ---> Object.prototype ---> null
console.log(o.a); //1
// a是o的自身属性吗?是的,该属性的值为1
console.log(o.b); //2
// b是o的自身属性吗?是的,该属性的值为2
// 原型上也有一个'b'属性,但是它不会被访问到。这种情况称为"属性遮蔽"
console.log(o.c); //4
// c是o的自身属性吗?不是,那看看原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为4
console.log(o.d); // undefined
// d是o的自身属性吗?不是,那看看原型上有没有
// d是o.[[Prototype]]的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null, 停止搜索
// 没有d属性,返回undefined