继承
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
原型链继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。实现的本质是重写SubType的原型对象。
问题:对象实例共享所有继承的属性和方法,包含引用类型的属性。
借用构造函数
解决原型链中包含引用类型值所带来的问题。
在子类型构造函数的内部调用超类型构造函数。这样可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
function SubType(){
// 继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
// 实例属性
this.age = 29;
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); // "red,blue,green,black"
var instance1 = new SubType();
alert(instance2.colors); // "red,blue,green"
通过使用call(),我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()中定义的所有对象初始化代码。结果SubType的每个实例就都会具有自己的name和colors属性的副本了。
问题:方法都在构造函数中定义,函数无法复用。超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
组合继承
将原型链和借用构造函数的技术组合到一块。使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
// 继承了SuperType的属性
SuperType.call(this, name);
// 实例属性
this.age = 29;
}
//继承方法
SubType.prototype = new SuperType();
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 instance1 = new SubType("Greg", 27);
alert(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg"
instance2.sayAge(); // 27
SuperType构造函数定义了两个属性:name和colors。SuperType的原型定义了sayName()。SubType构造函数在调用SuperType构造函数时传入了name参数,紧接着又定义了它自己的属性age。然后,将SuperType的实例赋值给SubType的原型,然后又在该新原型上定义了方法sayAge()。这样一来,就可以让两个不同的SubType实例既分别拥有自己的属性————包括colors属性,又可以使用相同的方法了。
问题:多次调用超类型构造函数而导致的低效率。
原型式继承
可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
var person = {
name : "Nicholas",
friends : ["Shelby","Court", "Van"]
}
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); // "Shelby,Court,Van,Rob,Barbie"
问题:和原型链一样,包含引用类型的属性始终都会共享相应的值。
寄生式继承
与原型式继承相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。
function createAnother(original){
var clone = Object.create(original);
clone.sayHi = function(){
alert("hi");
}
return clone;
}
createAnother()接收了一个参数,也就是将要作为新对象基础的对象。
然后创建一个original基础对象的副本clone。再为clone对象添加一个新方法sayHi(),最后返回clone对象。
var person = {
name : "Nicholas",
friends : ["Shelby","Court", "Van"]
}
var anotherPerson = Object.create(person);
anotherPerson.sayHi(); // "hi";
以上代码基于person返回了一个新对象————anotherPerson。新对象不仅具有person的所有属性和方法,而且还有自己的sayHi()方法。
问题:与借用构造函数相似,寄生式继承为对象添加函数,会由于不能做到函数复用而降低效率。
寄生组合式继承
集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式。
function inheritPrototype(subType, superType){
subType.prototype = Object.create(superType.prototype);
subType.prototype.constructor = subType;
}
- 创建超类型原型的一个副本,赋值给子类型的原型;
- 为子类型添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.protot.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.prototype上面创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能正常使用instanceof和isPrototypeOf()。