1.原型链
实现原型链的基本模式:
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
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
b继承a实际上就是创建a的实例,并将该实例赋予给b.prototype.实现的本质是重写原型对象,代之以一个新类型的实例。换句话说原来存在于a中的属性和方法现在也存在于b.prototype中了。通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。
所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例。所以所有的自定义类型都会继承toString()、valueof()等默认方法。
缺点:1.包含引用类型值的原型属性会被所有实例共享,(基本类型值也会被所有实例共享,但是子类型实例可以通过设置一个同名属性屏蔽超类型原型中的属性从而拥有自己的属性,而引用类型的值是会修改原型中的属性的,从而影响所有实例)这也是为什么要在构造函数中,而不是在原型对象中定义属性的原因。
2.在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
基于以上两点,实践中很少会单独使用原型链。
2.借用构造函数
实现构造函数的基本模式:
function SuperType(){
this.colors=["red","blue","green"];
}
function SubType(){
SuperType.call(this);//这样我们实际上是在将来构造的SubType实例环境中调用了SuperType构造函数。
}
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"
基本思想:在子类型构造函数的内部调用超类型构造函数。
函数只不过是在特定环境中执行代码的对象。调用call()方法,我们实际上就会在新SubType对象上执行SuperType函数中定义的所有对象初始化代码。结果每个SubType的实例就都会具有自己的colors属性的副本。
优点:1.可以在子类型中向超类型传递参数(相对于原型链而言)。
2.可以解决原型链继承所带来的引用类型值所带来的问题。
缺点:1.为了确保SuperType构造函数不会重写子类型的属性,必须在调用超类型构造函数后,再添加应该在子类型中定义的属性。(这也算不上什么缺点,原型链也是这样的,必须确保超类型的实例替换掉子类型的原型之后再定义自己的原型方法)
2.方法都在构造函数中定义了,因此函数复用就无从谈起了。
3.在超类型的原型中定义的方法,对子类型而言也是不可见的,(?因为这是原型链的原理决定的,只有子类型实例会继承超类型的属性和原型方法,而构造函数模式只是在子类型中引用了超类型构造函数)结果所有类型都只能使用构造函数模式。
故借用构造函数模式也是很少单独使用的。
3.组合继承
实现组合继承的基本模式:
function SuperType(name){
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继承SuperType的属性和方法时,可能破坏了constructor,重写constructor有利于将来创建SubType实例时,继承构造函数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";
基本思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
优点:1.可以让每个不同的实例拥有自己的属性,同时又共享了相同的方法。
2.避免了原型链继承和借用构造函数继承的缺点,融合了它们的优点。
缺点:1.无论什么情况下,都会调用两次超类型构造函数。
故组合继承模式成为了JS中最常用的继承模式。
而且,instanceof和isPrototypeOf ()也能用于识别基于组合继承创建的对象。
4.原型式继承
原型式继承的基本模式:
function object(o){
function F(){}
F.prototype=o;
return new F();
}
基本思想:借助原型可以基于已有的对象创建新对象。从本质上讲,object()对传入其中的对象执行了一次浅复制。
eg:
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"
ECMAScript5通过新增Object.create()方法规范了原型式继承。这个方法接受两个参数,1.用作新对象原型的参数2.(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()方法和object()方法的行为相同。
var anotherPerson=Object.create(person,{
name: {
value: "Greg"//每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
}
});
优点:1.在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
缺点:相信看到这里,不少朋友都看出门道了,原型式继承与原型链继承有相同的缺点,包含引用类型的值始终都会共享相应的值。
5.寄生式继承
基本模式:
function createAnother(original){
var clone=object(original);
clone.sayHi=function(){
alert("hi");
};
return clone;
}
基本思想:与原型式继承紧密相关,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象
优点:1.在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
2.object()函数不是必需的,任何能返回新对象的函数都适用于此模式。
缺点:1.适用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。
6.寄生组合式继承
先让我们来看看组合继承的不足。
function SuperType(name){
this.name=name;
this.colors=["red","blue","green"];
}
SuperType.prototype.sayName=function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name);//第二次调用超类型,同样会继承超类型的name和colors属性,会屏蔽原型第一次继承超类型的同名属性。
this.age=age;
}
SubType.prototype=new SuperType();//第一次调用超类型,会继承超类型的name和colors属性,(至于为什么第一次先调用原型,我认为和原型链的工作原理有关)
SubType.prototype.constructor=SubType;//。
SubType.prototype.sayAge=function(){
alert (this.age);
};
如上面的代码所示:当创建子类型的实例时,会在新对象上调用两次超类型,从而得到两组name和colors属性。一组位于实例中,一组在原型中。而解决这个问题的办法就是寄生组合式继承。
基本模式:
function inheritPrototype(subType,superType){
var prototype=object(superType.prototype);//return prototype。其中prototype的原型是superType.prototype
prototype.constructor=subType;
subType.prototype=prototype;
}
基本思路:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后将结果指定给子类型的原型。
这样我们可以改写前面的例子。
function SuperType(name){
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;
}
inheritPrototype(subType,superType);//只继承超类型原型中的方法!
SubType.prototype.sayAge=function(){
alert (this.age);
};
优点:1.它只调用了一次超类型的构造函数,效率较高。并且避免了在子类型的原型上创建不必要的,多余的属性。节省内存。
2。原型链还能保持不变。因此还能正常使用instanceof和isPrototypeOf()。
故我认为最理想的引用类型继承范式便是寄生组合式继承。