原型链是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());
上述代码没有使用SubType默认提供的原型,而是给了它换了一个新原型,这个新原型就是SuperType的实例。
调用instance.getSuperValue()会先 1:)搜索实例;2)搜索SubType.prototype; 3:)搜索SuperType.prototype,最后一步才找到该方法,在找不到属性或者方法的时候,搜索过程总是一环一环的进行到原型链末端才会停下来。
所有引用类型中默认都继承了Object,这个继承也是通过原型链实现的。
别忘记默认的原型:
确定原型和实例的关系:
通过两种方式确定原型和实例的关系,一种是使用instanceof操作符,另一种是使用isPrototypeOf()方法,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
alert(instance instanceof Object); //true
alert(instance instanceof SuperType) //true
alert(instance instanceof SubType) //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
谨慎地定义方法:
子类型有时候需要重写超类型的某个方法,或者添加某个不存在的方法,不管怎么样给原型添加代码一定要放在替换原型语句之后。
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;
};
//重写超类型中的方法
SubType.prototype.getSuperValue = function(){
return false;
};
还有一点需要注意的是 ,在通过原型链实现继承时,不能用对象字面量创建原型方法,因为这样会重写原型链。
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;
},
someOtherMethod : function(){
return false;
}
}
var instance = new SubType();
alert(instance.getSuperValue()); //error!
这样做了之后instance就包含的是一个Object的实例,而非SuperType的实例,因此我们设想的原型链已经被切断 - SubType和SuperType之间已经没有关系了。
原型链的问题:
1.包含引用类型值的原型属性会被所有实例共享,这是为什么要再构造函数中而不是在原型对象中定义属性的原因。
2.在创建子类型实例时不能像超类型的构造函数中传递参数,所以结合第一个原因在实践中很少单独使用原型链。
借用构造函数
解决原型包含引用类型值,开始使用一种叫做借用构造函数,即
在子类构造函数内部调用超类型构造函数。
function SuperType(name){
this.colors = name;
}
function SubType(){
SuperType.call(this,"Alice");
//实例属性
this.age = 23;
}
var instance = new SubType();
alert(instancel.name); //"Alice"
alert(instancel.age); //23
在SubType的环境下调用了SuperType构造函数。 这样一来就会在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。
1.相对于原型链而言,借用构造函数的有事就是可以在子类的构造函数中向超类的构造函数
传递参数。
function SuperType(){
this.color= ["red", "blue", "green"];
}
function SubType(){
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instancel1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instancel2.colors); //"red,blue,green"
2.借用构造函数的问题,方法都在构造函数中定义,函数的复用就无从谈起了,而且在超类型中定义的方法,对子类型而言是不可见的,结果所有的类型只能使用构造函数模式。所以借用构造函数的技术也很少单独使用。
组合继承
组合继承指的是将原型链和借用构造函数的技术组合到一块,发挥二者之长的一种继承模式。 思路是使用原型链实现对原型属性和方法的继承,使用借用构造函数实现对实例属性的继承。
这样,原型的方法实现了函数的复用,又能保证每个实例都有他自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red", "green", "yellow"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
funciton SubType(name, age){
//继承属性
SuperType.call(this, name); //借用构造函数实现对实例属性的继承
this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); //原型链实现对属性和方法的继承
//自己的方法
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("Alice",23);
instance1.colors.push("black");
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType("Bob",22);
alert(instance2.colors);
instance2.sayName();
instance2.sayAge();
这样两个instace既有自己的属性,又可以使用相同的方法了,组合继承避免了原型链和构造函数的缺陷,融合了他们的优点,
成为了JacaScript中最常用的集成模式,而且instanceof()和isPrototypeOf()也能够用于识别给予组合继承创建的对象。
原型式继承
ECMAScript5通过新增Object.create()方法规范化了原型式继承。 这个方法接受两个参数,一个作为
新对象的原型对象和一个
可选的新对象定义额外的属性对象。
var person = {
name:"Alice",
firends:["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "robret";
anotherPerson.firends .push("Rob");
var anotherPerson1 = Object.create(person);
anotherPerson1.name = "gooose";
anotherPerson1.firends .push("Barbie");
alert(person.friends);
/* 定义两种参数的Create */
var person = {
name : "Alice",
firends : ["shelby", "court", "van"];
};
var anotherPerson = Object.create(person,{
name:{
value:"goose"
}
});
alert(anotherPerson.name);//"goose"
只是让一个对象和另一对象保持类似的情况下,原型式继承可以完全胜任。
寄生式继承
思路和工厂模式类似,创建一个用于封装继承过程的函数,该函数子内部以某种方式来增强对象,最后再像做了所有工作一样返回对象。
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert("HI");
};
return clone;
}
使用这个函数
var person = {
name: "Alice",
friends : ["Shellby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //hi
如果不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
寄生组合式继承
组合继承是javascript最常用的继承模式,不过它的不足时无论什么情况下,都会调用两次超类型的构造函数; 一次是在创建子类型原型的时候,另一个是在子类构造函数内部。
function SuperType(name){
this.name = name;
this.color= ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name); //2
this.age = age;
}
SubType.prototype = new SuperType(); // 1
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance = new SubType("Alice",23);
调用SuperType的构造方法,一次是在SuperType构造函数时,
SubType。prototype会得到 两个属性name和colors会存在在SubType的原型中,又一次调用SuperType的构造函数,这一次是在SubType的对象实例上创建了两个属性name.和color;
这样会出现两组name和colors属性,一种在实例上,一种在原型中,这就是调用两次SuperType函数的结果。 现在找到了解决这种问题的方法:
寄生组合式继承
所谓的寄生组合继承,通过借用构造函数来继承属性,通过原型链的混形式来继承方法, 避免为了指定子类型的原型而调用超类型的构造函数,我们需要的就是超类型原型的一个副本而已。
基本模式如下:
function inheritPrototype(SubType, SuperType){
var prototype = Object(SuperType.prototype);
prototype.constructor = SubType;
subType.prototype = prototype;
}
//这个函数接受两个参数,子类型的构造函数和超类型的构造函数,先创建超类型的一个副本,为创建的副本添加constructor属性,之后将创建的对象副本赋值给子类型的原型,这样就可以调用inheritPrototype()函数省略为子类型原型赋值的语句了。
function SuperType(name){
this.name = name;
this.colors = ["red", "green", "yellow"];
}
SuperType.protototype.SayName = function(){
alert(this.name);
}
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.SayAge = function(){
alert(this.age);
}
这个例子高效的体现它只调用一次SuperType构造函数,因此避免了在SubType上创建多余的不必要的属性,与此同时原型链还能保持不变,还能够正常使用instanceof和isPrototypeOf(),寄生组合式继承是引用类型最理想的继承范式。
总结: JavaScript主要通过原型链实现继承, 原型链的构建是讲一个类型的实例赋值给另一个构造函数的原型实现的。这样子类就能够访问超类的所有属性和方法,这一点和类的继承很相似,原型链的问题是对象实例共享所有继承的属性和方法,因此不适合单独使用。 解决这个问题的技术是借用构造函数,在子类型的内部调用超类的构造函数。 这样就能做到每个实例都有自己的属性,同时还能保证只是用构造函数模式来定义类型。 使用最多的继承模式是组合继承,这种模式使用原型链来继承共享的属性和方法,通过借用构造函数继承实例属性。
此外还有三种继承模式:
原型式继承,寄生式继承,寄生组合式继承。