在ECMAScript中实现继承主要是依靠原型链来实现的,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
一、原型链
每个构造函数都有一个原型对象,,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针,如果我们让原型对象等于另一个类型的实例,那么此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型有事另一个类型的实例,那么上述关系仍然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
实现原型链有一种基本模式,代码如下:
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subproperty=false;
}
//继承了SuperType
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true
子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后,以及,在通过原型链实现继承时不能使用对象字面量创建原型方法。
原型链的问题:最主要的问题是原型中包含引用类型值的问题,第二个为题是在创建子类型的实例时,不能向超类型的构造函数传递参数。所以实践中很少会单独使用原型链。
二、借用构造函数(伪造对象或经典继承)
这种技术的基本思想想当简单,即在子类型构造函数的内部调用超类型构造函数。通过使用apply()和call()方法也可以在新创建的对象执行构造函数
function SuperType(){
this.colors=['red','blue','green'];
}
function SubType(){
//继承了SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('black');
alert(instance1.colors);//'red,blue,green,black'
var instance2 = new SubType();
alert(instance2.colors);//'red,blue,green'
相对于原型链,借用构造函数模式有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this,'Nicholas');
this.age = 29;
}
var instance = new SubType();
alert(instance.name);//'Nicholas'
alert(instance.age);//29
在SubType构造函数内部调用SuperType构造函数时,实际上是为SubType的实例设置了name属性。为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性
借用构造函数模式的问题:如果仅仅是借用构造函数,那么方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法对子类型而言是不可见的。所以借用构造函数的技术也是很少单独使用。、
三、组合继承(伪经典继承)
是将原型链和借用构造函数的技术组合到一块。从而发挥二者之长的一种继承模式.。其思路是使用原型链实现对原型属性和方法的继承。而通过借用构造函数来实现对实例属性的继承。
function SuperType(){
this.name = name;
this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function (){
alert(this.name);
};
function SubType(name,age){
//继承属性
SuperType.call(this,name)
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType('Nicholas',29);
instance1.colors.push('black');
alert(instance1.colors);//'red,blue,green,black'
instance1.sayName();//'Nicholas'
instance1.sayAge();//29
var instance2 = new SubType('Greg',27);
alert(instance2.colors);//'red,blue,green'
instance2.sayName();//'Greg'
instance2.sayAge();//27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。
四、原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name : 'Nicholas',
friends : ['Shelby','Court','Van']
};
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(person.friends);//'Shelby,Court,Van,Rob,Barbie'
在ES5中,通过新增Object.create()方法规范化了原型式继承。接收两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象
var person = {
name : 'Nicholas',
friends : ['Shelby','Court','Van']
}
var anotherPerson = Object.create(person,{
name : {
value : 'Greg'
}
});
alert(anotherPerson.name);
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承时完全可以胜任的。不过,包含引用类型的属性始终都会共享相应的值,就像使用原型模式一样。
除以上继承方式外,还有寄生式继承和寄生组合式继承方式。