由于函数没有签名,在ES中,无法实现接口继承。ES只支持实现继承,而且其实现继承主要是依靠原型链来实现的
原型链
ES中描述了原型链的概念,并将原型链作为实现继承的主要方式。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。实现原型链的基本模式如下:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue); //true
继承是通过创建SuperType的实例,并将该实例赋值给SubType.property实现的。关系图如下:
在上面的代码中,我们没有使用SubType默认提供的原型,而是给他换了一个新原型;这个新的原型就是SuperType的实例。于是,新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且内部还有一个指针,指向了SuperType的原型。
别忘记默认的原型,所有的引用类型都默认继承Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这正是所有自定义类型都会继承toString(),valueOf()等默认方法的原因
确定原型和实例的关系
第一种方式是通过使用instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
第二种方式是使用isPropertyOf()方法。只要是原型链中出现过的原型,都可以说是改原型链所派生的实例的原型。
alert(Object.prototype.isPropertyOf(instance)); //true
alert(SupertType.prototype.isPropertyOf(instance)); //true
alert(SubType.prototype.isPropertyOf(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;
}
var instance = new SubType();
alert(instance.getSuperValue); //false
在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型方法。
原型链的问题
包含引用类型值的原型属性会被所有实例共享;这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承的时候,原型实际上就会变成另一个类型的实例。于是,原型的实例属性也就顺理成章的变成了现在的原型属性。
function SuperType(){
this.colors = ["red","green","blue"];
}
function SubType(){}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("yellow");
alert(instance1.colors); //"red,green,blue,yellow"
var instance2 = new SubType();
alert(instance2.colors); //"red,green,blue,yellow"
原型链的第二个问题是:在创建子类型实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。有鉴于此,再加上上面说的原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链
借用构造函数
在解决原型链中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数的技术(有时间也叫伪造对象或者经典继承)。这种技术的基本思想非常简单,即在子类型构造函数的内部调用超类型构造函数
function SuperType(){
this.colors = ["red","green","blue"];
}
function SubType(){
SuperType.call(this); //继承了SuperType
}
var instance1 = new SubType();
instance1.colors.push("yellow");
alert(instance1.colors); //"red,green,blue,yellow"
var instance2 = new SubType();
alert(instance2.colors); //"red,green,blue"
通过使用call()方法(或apply()方法也可以),我们实际上是在(未来将要)新创建的SubType实例的环境下调用SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象代码初始化。结果就是SubType的每个实例就都会具有自己的colors属性的副本了。
1.传递参数
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。
function SuperType(){
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this,"zgx");
//实例属性
this.age = 18;
}
var instance = new SubType();
alert(instance.name); //"zgx"
alert(instance.age); //18
2.借用构造函数的问题
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题--方法都在构造函数中定义,因此函数复用就无从谈起了,而且,在超类型的原型中定义的方法,对于子类型而言也是不可见的。结果所有类型都只能使用构造函数模式,考虑到这些问题,借用构造函数的技术也是很少单独使用的
组合继承
组合继承,有时候也叫做伪经典继承。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,有能够保证每个实例都有自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
//继承属性
SuperType.call(this,"zgx");
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("zgx",19);
instance1.colors.push("yellow");
alert(instance1.colors); //"red,green,blue,yellow"
instance1.sayName(); //"zgx"
instance1.sayAge(); //19
var instance2 = new SubType("lyh",18);
alert(instance1.colors); //"red,green,blue"
instance1.sayName(); //"lyh"
instance1.sayAge(); //18
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的有点,成为js中最常用的继承模式。而且,使用instance和isPropertyOf()也能够识别基于组合继承创建的对象
原型式继承
借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
从本质上讲,object()对传入的对象执行了一次浅复制。来看下面的例子:
var person = {
name:"zgx",
friends:["lyh","cxx"]
};
var anotherPerson = object(person);
anotherPerson.name = "ht";
anotherPerson.friends.push("friend1");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "ld";
yetAnotherPerson.friends.push("friend2");
alert(person.friends); //"lyh,cxx,friend1,friend2"
ES5通过新增Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与object()方法的行为相同。Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
在没有必要兴师动众地创建构造函数,而只想让一个对象始终与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样
寄生式继承
寄生式继承与原型式继承是一种思路。即创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert("hihi");
}
return clone;
}
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。
寄生组合式继承
组合继承是js中最常用的继承模式,不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型的构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
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);
};
在第一次调用SuperType构造函数的时候,SubType.prototype会得到两个属性:name和colors;它们都是SuperType的实例属性,只不过现在位于SubType的原型中,当调用SubType构造函数时,它又调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中两个同名属性。如下图所示:
初始化
SubType.prototype = new SuperType();
var instance = new SubType("zgx",18);
继承组合式继承能解决这个问题,所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成方形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上就是使用寄生式继承超类型的原型。然后再将结果指定给子类型的原型。
继承组合式继承的基本模式如下所示:
function inheritProperty(subType,superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
这是实现寄生组合继承的最简单形式。这个函数接收两个参数,子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我们就可以用调用inheritProperty()函数的语句,去替换前面例子中为子类型原型赋值的语句了。例如
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,"zgx");
this.age = age;
}
inheritProperty(SubType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
这个例子高效率体现在它只调用一次SuperType构造函数,并且因此避免了在SubType.prototype上面创建不必要的,多余的属性。于此同时,原型链还能保持不变;因此能正确的使用instance和isPropertyOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。