1.原型链法
原型链是JavaScript中继承的主要方法。
每个构造函数都拥有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),实例都包含一个指向原型对象的内部指针(proto)。如果将一个类的实例赋值给另一个类的原型会这样?像下面的代码:
//定义父类
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
Person1.prototype={
constructor:Person,
sayName:function(){
console.log(this.name);
}
}
//定义子类
function Chinese(){}
//将它的原型替换为Person类的实例
Chinese.prototype=new Person('xiaohua',20,'gongren');
//定义子类实例,并调用sayName方法
var b=new Chinese();
b.sayName();//xiaohua
由此可知,子类已经可以使用父类的属性和方法。
原理:重写子类原型对象,用父类的实例赋值给Chinese.prototype实现继承,这时Chinese类的原型被修改了,新原型对象是Person的实例,那么Chinese.prototype就有了跟这个Person的实例一样的属性和方法(这里是name,age,job三个属性),那么子类实例当然可以使用子类原型中的这些属性和方法,这个新原型会包含指向另一个原型的指针(proto),这样构成了原型链。
子类实例在调用sayName方法时,会经历3个先后步骤:(1)搜索实例本身 (2)搜索Chinese类的原型 (3)沿着原型链搜索Person类的原型。最后在这里找到了这个方法。下面是类,原型和实例的关系图
原型链方法最主要的问题还是来自有引用类型的原型,我们以前说过包含引用类型值的原型属性会被所有实例共享,这也是为什么要在构造函数中定义属性,而不是在原型中定义属性的原因了。这里如果在Person的构造函数里,增加一句this.friends=[“Shelby”,”Court”,”Van”];,那么Chinese的原型中就会引用类型变量friends,所有Chinese实例共享引用类型的问题就出现。
2.借用构造函数
在解决原型中包含引用类型值所带来的问题的过程中,我们可以使用一种叫借用构造函数的技术(又称伪造对象或经典继承)。就是在子类型构造函数的内部调用父类型构造函数,记住,函数只不过是在特定环境中执行代码的对象,可以通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数。
function Person(){
this.friends=["Shelby","Court","Van"];
}
function Chinese(){
//继承了Person
Person.call(this);
}
var a =new Chinese();
a.friends.push("Jack");
alert(a.friends); //"Shelby","Court","Van","Jack"
var b=new Chinese();
alert(b.friends); //"Shelby","Court","Van"
借用构造函数的问题
如果仅仅使用借用构造函数,那么将无法避免构造函数模式存在的问题——方法都在构造函数中定义,函数的复用也无从谈起。所以借用构造函数也很少单独使用。
3.组合继承
组合继承(伪经典继承),指的是将原型链和借用构造函数的技术组合在一块。其思路是使用原型链对共享属性和方法的继承,而通过借用构造函数来实现对实例对象的继承。这样,既通过在原型上定义的方法实现了函数复用,又能保证每个实例都有它自己的属性。
function Person(name){
this.name=name;
this.friends=["Shelby","Court","Van"];
}
Person.prototype.sayName=function(){
alert(this.name);
}
function Chinese(name,age){
//继承属性
Person.call(this,name);
this.age=age;
}
Chinese.prototype=new Person();
Chinese.prototype.constructor=Chinese;
Chinese.prototype.sayAge=function(){
alert(this.age);
}
var a=new Chinese("Henry",29);
a.friends.push("Jack");
alert(a.friends); //"Shelby","Court","Van","Jack"
a.sayName(); //Henry
a.sayAge(); //29
var b=new Chinese("Greg",28);
alert(b.friends); //"Shelby","Court","Van"
b.sayName(); //Greg
b.sayAge(); //28
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的有点,成为JavaScript中追尾常用的继承模式。而且,instanceof和isPrototypeof()也能够用于识别组合继承创建的对象。
4.原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必创建父类构造函数和实例化。
function object(o){
function F(){};
F.prototype=o;
return new F();
}
在object()函数内部,先创建一个临时性的构造函数,然后将传入的对象作为该构造函数的原型,最后返回这个临时类型的一个新实例。本质上讲,object()对传入其中的对象进行了一次浅复制,也就是用一个对象来覆盖一个临时性的构造函数的原型,这样这个构造函数的实例就继承那个传递对象的属性和方法。
但包含引用类型值的属性始终会共享相应的值,这和原型模式一样的。如下:
function object(o){
function F(){};
F.prototype=o;
return new F();
}
var person={
name:"Henry",
friends:["Shelby","Court","Van"]
};
var a=object(person);
a.name="Greg";
a.friends.push("Rob");
var b=object(person);
b.name="Linda";
b.friends.push("Brabie");
alert(person.friends); //"Shelby,Court,Van,Rob,Brabie"
5.寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式类增强对象,最后再像真的是它做了所有工作一眼放回对象。
function creteAnother(original){
var clone=object(original); //通过调用函数创建一个新对象
clone.sayHi=function(){ //以某个方式来增强这个对象
alert("hi");
}
return clone; //返回这个对象
}
var person={
name:"zxj",
friends:["Shelby","Court","Van"]
};
var anotherPerson=createAnother(person);
anotherPerson.sayHi(); //"hi"
使用寄生式来为对象添加函数,会由于不能做到函数服用而降低效率。这一点与构造函数模式类似
6.寄生组合式继承
组合继承是JavaScript中最常用的继承模式,但它无论在什么情况下,都会调用两次父类型构造函数:一次在创建子类型原型时,一种在子类型构造函数内部。
function SuperType(name){
this.name=name;
this.colors=["red","blue","green"];
}
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);
}
这样导致的结果是,实例中和子类原型中都包含了相同的属性name和colors,只是运行时实例中的属性屏蔽了子类原型中的属性,这样的话原型中的这些属性就没有存在的必要了。
而寄生组合式继承就能解决这个问题,所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链来继承方法。思路:不必为了指定子类型的原型而调用父类型的构造函数,我们只需父类型原型的一个副本。本质上,就是使用寄生式继承来继承父类型的原型,然后将结果指定给子类型的原型,寄生组合式继承的基本模式如下:
function inheritPrototype(subType,superType){
var prototype=object(superType.prototype); //创建对象
prototype.constructor=subTyoe; //增强对象
subType.prototype=prototype; //指定对象
}
示例中inheritPrototype()函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和父类型构造函数。在函数内部:第一步是创建父类型原型的一个副本。第二步是为创建的副本调价constructor属性,从而弥补因重写原型而失去默认的constrcutor属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我就可以调用inheritPrototype()函数的语句,去替换前面例子中为子类型原型赋值的语句。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //create object
prototype.constructor = subType; //augment object
subType.prototype = prototype; //assign object
}
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);
};
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