简单回顾原型链
原型链
我们每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针constructor,而实例都包含一个指向原型对象的内部指针proto。如果我们让原型对象等于另一个类型的实例,这时原型对象将包含指向另一个原型的指针,另一个原型中也包含指向另一个构造函数的指针。 假如另一个原型又是另一个类型的实例,那么上述关系成立,层层递进,构成了实例与原型链。
原型链继承
举例
function SuperType(){
this.property=true;
}
Super.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subProperty=false;
}
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
//
var instance=new SubType();
SubType继承了SuperType。
怎么继承的?
我们把SuperType的实例赋给SubType的prototype了。
实现的本质是重写原型对象!
其实我们是没有使用SubType的默认提供的原型的!
而是换了一个原型,这个原型对象就是SuperType的。
instance–> SubType–>SuperType
你会发现property是SuperType的实例属性,这个属性存在于SubType的原型中!
其实这也印证了JS中只有对象,没有类的事实。
再看constructor
而instance的constructor现在指向了SuperType,因为instance是SubType的实例,而SubType的原型指向了另一个对象SuperType的原型,这个原型对象的constructor是SuperType,所以SubType的员阿里的默认constructor已经不复存在了!
默认原型
所有函数的默认原型都是Object的实例。
默认原型包含一个内部指针__proto__,指向Object.prototpe
确定原型和实例的关系
instanceof 操作符
由于原型链的关系,instance是Object SubType SuperType的任何一个类型的实例。
测试这三个构造函数都返回了true。
isPrototypeOf()
只要是原型链出现过的原型,都可以说是该原型链派生的实例的原型。都会返回true
instance instanceof Object ; //true
Object.prototype.isPrototypeOf(instance);//true
添加/定义方法
通过原型链,扩展了原型搜索机制。我们给原型添加方法的代码一定要放在替换原型的语句之后。
function SuperType(){
this.property=true;
}
Super.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subProperty=false;
}
//继承了SuperType
SubType.prototupe=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subProperty;
}
//
SuperType.prototype.getSuperValue=function(){
//重写了继承的方法
}
var instance=new SubType();
重写继承的方法会屏蔽原来的方法。因为原型搜索的缘故。
但是对于SuperType的实例来说,它还是调用本来的那个方法,因为它的原型并没有改变。
不能使用对象字面量创建原型方法,否则就会重写原型链。
原型链的问题
1. 第一个问题是,也是最主要的问题就是,包含引用类型值的原型属性会被共享。通过原型来实现继承时,原型实际上会变成另一个类型的实例。 于是原来的实例属性也就称为了现在的原型属性了。
例如:
function SuperType(){
this.colors=['red','g','b'];
}
function SubType(){
}
//继承了SuperType
SubType.prototype=new SuperType();
var instance1=new SubType();
instance1.colors.push('blc');
//instance1.colors ['red','g','b','blc']
定义这个colors属性,它是SuperType的实例属性,但是我们SubType.prototype=new SuperType();
就把SuperType的实例属性赋给SubType.prototype了,它的原型也就拥有了这个属性。
相当于:
创建了一个SubType.prototype.color属性!
2.第二个问题
创建子类型的实例时,不能向超类型的构造函数传递参数。 没有办法在不影响所有对象实例的情况下,给超类型构造函数传递参数。 因为这样就会导致子类型的原型也有这些属性了。
借用构造函数
在子类型构造函数内部调用超类型构造函数。
function SuperType(){
this.colors=['red','g','b'];
}
function SubType(){
//继承了SuperType
SuperType.call(this);
}
我们借用了超类型的构造函数,通过使用call方法,实际上就是在SubType实例环境下调用了SuperType构造函数。
这样就可以在SubType的每个实例上拥有自己的colors属性的副本了。
借用构造函数的问题
同样还是构造函数模式的问题,函数无法复用!
在这种模式下,超类型的原型中定义的方法,对子类型而言也是不可见的。
组合继承
使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承!
function SuperType(name){
this.name=name;
this.colors=['red','g','b'];
}
SuperType.prototype.sayName=function(){
}
function SubType(name,age){
//继承了属性 ,SubType的实例可以有不同的实例属性
SuperType.call(this,name);
this.age=age; //自己的属性
}
//继承方法
SubType.prototype=new SuperType();
//constructor指回SubType构造函数,不然就是SuperType的构函数,但是其实改变这个值没什么影响
SubType.prototype.construcotr=SubType;
SubType.prototype.sayAge=function(){
alert(this.age);
}
SuperType的实例赋给SubType.prototype之后,使得SubType可以继承SuperType的方法(其实就是原型链能搜索到)。
然后SuperType的属性都会变成SubType的原型属性。 但是我们调用了 SuperType.call(this,name),导致SubType就可以有自己的实例属性了! 重新执行了一次这个构造函数。
原型式继承
借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。
道格拉斯:
function object(o){
function F(){}
F.prototype=o;
return new F();
}
在这个object函数内部,有一个临时构造函数,然后把传入的对象赋给它的原型(就是把原型设为这个传入对象),最后返回了这个临时类型的一个新实例。
本质
执行了一次浅复制
我们传入的对象是我们的基础。 返回的新对象是将person作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性。
var person={
name:"NI",
friends:[1,2,3]
}
var anotherPerson=object(person);
var yetPerson=object(person);
我们新创建的对象就会将person作为原型,然后它的原型中就包含一个基本类型值属性和一个引用类型值属性。
person.friends这个属性不仅在person有,anotherPerson yetPerson也会共享这个属性。
相当于创建了person对象的两个副本。
ES5 使用Object.create()
就等于这个方法。 传入两个参数,
一个用作新对象原型的对象 和 一个为新对象定义额外属性的对象~!
var anotherPerson=Object.create(person);
var yetPerson=Object.create(person);
缺点:
引用类型值的属性始终会共享相应的值。相同的值。和原型模式一样
寄生式继承
思路类似工厂模式/寄生构造模式。 所谓寄生即依赖一个对象
创建一个仅用于封装继承过程的函数。
该函数内部以某种方式来增强对象。
function createAnother(original){
//创建了一个新对象
var clone=object(original);
//增强这个对象
clone.sayHI=function(){};
//返回这个对象
return clone;
}
对于寄生继承来说,主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。 我们不一定要使用object(),任何能够返回新对象的函数都适用这个模式!
问题:
寄生式继承,仍然无法做到函数复用。
寄生组合式继承
JS最常用的继承模式。
组合继承的问题:无论什么情况下都会调用两个超类型构造函数。
看我们前面的代码就可发现:
SuperType.call(this,name);//second
SubType.prototype=new SuperType();//first
我们第一次调用new SuperType绑定原型给SubType的时候,我们的原型对象prototype就会得到这两个属性。
当我们第二次调用这个构造函数时,就在上面创建了实例属性name和colors。于是这两个属性就屏蔽了原型中的两个同名属性~!
我们两组属性,一组在实例,一组在SubType原型中。寄生组合:借用构造函数继承属性,原型链混成形式来继承方法。
关键:不必为了指定子类新的原型而调用超类型的构造函数,我们需要的只是超类型原型的一个副本~!
function inheritPrototype(subType,superType){
//创建对象,就是创建超类型的一个副本
var prototype=object(superType.prototype);
//为创建的副本添加constructor,因为重写原型会失去默认的这个constructor属性
prototype.constructor=subType;
//将新创建的对象(副本)赋值给子类新的原型。
subType.prototype=prototype;
}
function SuperType(name){
this.name=name;
this.colors=['red','g','b'];
}
SuperType.prototype.sayName=function(){
console.log(this.name);
};
function SubType(name,age){
SuperType.call(this,name);
this.age=age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge=function(){
}
它的高效率体现在只调用了一次SuperType构造函数。避免了在SubType.prototype上创建不必要多余的属性
寄生组合继承是引用类型的最理想继承方式!