原型链继承
书中开始有这段话:重温一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个指针指向原型。
如果学过构造函数创建对象的方法就知道这段话引入了三个概念:
- prototype (原型对象)
- contructor(构造器)
- __proto__ (原型)
而后面有地狱级的一句话:
如果原型是另一个类型的实例呢?
先不去理会这句话的重量,我们先实现一个最简单的原型链,只有三个元素:实例 instance、构造函数 SubType、构造函数的原型对象SubType.prototype。
我们先不去管内部的属性让代码简单一些:
function SubType(){}
let instance = new SubType();
目前已经创建了一个构造函数SubType,并使用该构造函数生成了一个实例 instance,且该构造函数有一个原型对象 SubType.prototy。
将这段关系套入红宝书描述的那段话中,SubType有一个原型对象——SubType.prototype,SubType.prototype上有一个属性(contructor)指回构造函数SubType,实例(instance)有一个指针(__proto__)指向SubType.prototype,具体如下图所示,先不要去管实例的原型身上的__proto__属性(和地狱级的那句话有关)。
*chromium内核的浏览器中\_\_proto__通常是 [[Prototype]]*
这张图的SubType的箭头应该是从prototype属性指向SubType.prototype
当我们理清楚这段关系后,就可以思考下 >>> 如果原型是另一个类型的实例呢?<<< 这句话了。
如果目前的原型SubType.prototype也是一个实例时,那就需要引入另外两个元素,假设为SuperType和SuperType.prototype(SuperType的实例就是SubType.prototype)
同样用不带属性的对象实现一下
function SuperType(){}
function SubType(){}
SubType.prototype = new SuperType();
let instance = new SubType();
正如书中所言,目前已经实现了SubType对SuperType的继承,SubType的实例可以访问到SuperType和SuperType.property的属性和方法,但与此同时,SubType.prototype失去了指向SubType的contructor,因为目前SubType的原型对象是我们自己指定的,是没有contructor这个属性的。
我们通过一个更完整的例子实现下这套代码:
function SuperType(){
this.location = 'supertype';
}
SuperType.prototype.getSuperValue = function(){
return this.location;
}
function SubType(){
this.sublocation = 'subtype';
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.sublocation;
}
let instance = new SubType();
/*===================================*/
console.log('instance is:');
console.dir(instance);
console.log(instance.getSuperValue()); //supertype
console.log(instance.getSubValue()); //subtype
此时实例对象可以访问到SuperType才应该有的属性和方法,此时原型链图如下所示:
可以通过浏览器打印instance得到下面的对应关系:
可见该实例由SubType生成,本身带有sublocaton属性,且可以访问到构造函数原型对象上的属性location和方法getSubValue,同时可以依靠原型的原型上的方法getSuperValue,同时也可知该实例的原型是SuperType的实例(原型的原型的constructor指向SuperType)
原型继承的缺陷
我们可以使用instanceof的方法来判断对象的继承关系,但原型链的继承关系是很脆弱的,因为这种继承方式已经破坏了构造函数的原型对象上的contructor属性,也很难解释下面这种情况
随意定义一个函数就有一个contructor的属性,而我们使用构造函数的原型直接等于另一个构造函数的实例会丢失这个指向, 这与我们使用的JS完全背道相驰。
并且我们目前无法给父类进行赋值,而父类的属性是通过原型链进行访问的,而不是成为自己的私有属性,因此对父属性引用类型进行修改,会影响到所有实例
function SuperType(){
this.location = 'supertype'
this.attr = {name:'superAttr'}
}
SuperType.prototype.getSuperValue = function(){ return this.location}
function SubType(){
this.sublocation = 'subtype'
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function(){ return this.sublocation }
let instance = new SubType()
let instance2 = new SubType()
console.log('instance attr is',instance.attr.name) //instance attr is superAttr
console.log('instance2 attr is',instance2.attr.name) //instance attr is superAttr
instance.attr.name='SuperAttribute'
console.log('instance attr is',instance.attr.name) //instance attr is superAttribute
console.log('instance2 attr is',instance2.attr.name) //instance attr is superAttribute
下面是一个理想中的对象的继承:
总结
虽然实现了一种充满缺陷的继承方式,但是我们至少也收获了原型链的知识,也明白可以通过原型链访问到更高层级类型的方法,且原型链的访问可以被下面等级的替代。