您的位置:首页 > Web前端 > JavaScript

javascript面向对象技术之继承

2013-04-17 21:13 190 查看
在学习继承之前,我们要补充几点关于创建对象的知识。在上一节我们讲过,创建对象有好几种方法,其中最常用的方法是混杂模式创建对象,我们说到,混杂模式创建对象有一个缺点是构造函数和原型方式是分开的,这一点可能会显得不能够体现封装的概念。那么,我们可以考虑如何把构造函数和原型放在一起封装起来呢?请看如下代码:

1  function Person(name,age){
2   this.name=name;
3   this.age=age;
4
5   if(typeof this.sayname !="function"){
6       Person.prototype.sayname=function(){ //sayname方法不存在才执行
7
8 alert(this.name);
9  }
10 }
11}


以上代码其实就是动态原型模式创建对象的方法,动态原型模式有一个缺点,无法使用对象字面量重写原型,因为重写原型就会切断现有实例和新原型直接的联系,即constructor属性指向了object。

原型链继承

接下来,我们要讲解javascript如何实现继承机制的?所谓继承,就是指一个对象拥有另一个对象的方法和属性。在上一节,我们讲过javascript中最重要的概念之一原型,本质上就是所有函数都有一个prototype属性,指向一个所有实例共享的原型对象。原型链继承的机制也是利用这一点来实现的。如果我们将子类型的原型对象等于父类型的实例(Child.prototype=new Father()),就相当于将子类型的原型对象指向了父类型的原型对象,并且子类型也得到了父类型的实例上的所有属性和方法,这样就构成了一个所谓的原型链,本质上就是各类型的原型链接起来了,这样子类型就继承了父类型的属性和方法,模拟出继承的机制。一句话,原型链继承就是重写了子类型的原型对象,将父类型的实例属性和方法继承到子类型的原型对象中,并且与父类型的原型对象链接起来了。

请看以下示例图(引用图片来自李炎恢教程PDF):





通过以上示例图我们可以看到,table的原型对象链接到了desk,desk又链接到了box,box实际上链接到了object,这一步是javascript自动帮我们完成的。

那么,代码如何写呢?请看以下代码(省略了Table继承,此代码引用了李炎恢教程的PDF):

function Box(){
this.name="sun";
}
function Desk(){
this.age="10";
}
Desk.prototype=new Box();//子类型的原型等于父类型的实例,形成原型链

var desk=new Desk();
alert(desk.age);//10


OK!了解了原型链的继承,那么我们会发现原型链的继承有一些缺点,和原型创建对象一样,原型链继承也会导致一些不该被共享的属性共享了,使得某些实例修改了该属性导致全部实例也修改了该属性。例如以下代码:

function SuperType(){
this.colors=["red","blue","green"];
}
function SubType(){}
SubType.prototype=new 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 black


可以看到,instance2和instance1都有同样的colors属性。如何解决呢?正如上一节讲过的,我们可以利用在构造函数里定义这些不需要被共享的属性。那么,如何利用构造函数实现继承呢?基本思想是在子类型的构造函数中调用父类型的构造函数,具体请看以下代码:

function SuperType(name){
this.name=name;
this.colors=["red","blue","green"];
}
function SubType(name,age){
SuperType.call(this,name); //继承了SuperType,这叫做对象冒充
this.age=age
}

var instance1=new SubType("sun","24");
instance1.colors.push("black");
//alert(instance1.colors);
var instance2=new SubType("tian","23");
alert(instance2.colors);


以上代码中使用了call()方法,实现了继承。call()方法接受2个参数,第一个参数是运行函数的作用域,第二个参数是运行函数的参数。所谓运行函数就是调用此call()方法的函数,call()方法是函数内部的方法。例如在上面代码中,call()方法的运行函数就是SuperType构造函数,this指的是当前对象。利用构造函数实现继承也有一个问题就是方法不能复用,那么很明显我们可以将原型链和构造函数的继承方法组合起来实现组合继承。请看以下代码:

function SuperType(name){
this.name=name;
this.colors=["red","blue","green"];
}
SuperType.prototype={
constructor:SuperType,
sayname:function(){
alert(this.name);
}
};
function 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("sun","24");

var instance1=new SubType("wang","23");


组合继承的缺点是无论在什么情况下,都会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部,这样导致的结果就是有两组name和colors属性,一组在实例上,另一组在SubType原型中。

原型式继承

基本思想:借助原型可以基于已有的对象创建新对象。请看以下函数代码:

function objectClone(object){
function F(){}
F.prototype=object;
return new F();
}

这段代码的意思是在函数内部先创建一个临时的构造函数F(),然后将F的原型继承已有的对象object,最好返回F对象的一个实例,这样的结果就是创建了一个继承了已有对象object的新对象。然后再以某种方式加强新对象,实现其他功能。例如以下代码:

function objectClone(object){
function F(){}
F.prototype=object;
return new F();
}
var person={
name:"sun",
friends:["tian","wang"]
};
var anotherperson=objectClone(person);
anotherperson.name="fang";
anotherperson.friends.push("liu");


ECMAScript 5 新增了一个Object.creat()方法规范了原型式继承。这个方法接收2个参数,一个用作新对象原型的对象,一个为新对象定义额外属性的对象。在没有必要兴师动众的创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承完全可以胜任。但是不要忘记了,包含引用类型值的属性始终都会共享相应的值。

寄生式继承

先看以下代码:

function createObject(origianl){
var clone=objectClone(original);
clone.sayhi=function(){
alert("Hi");
};
return clone;
}
var person={
name:"sun",
friends:["tian","wang"]
};
var anotherperson=creatObject(person);
anotherperson.sayhi();


寄生式继承的思路与工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回这个对象,以上面的例子,基于person对象返回了一个新对象anotherperson,它不仅具有person的所有属性和方法,也有自己的sayhi方法。在主要考虑对象不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

寄生组合式继承

前面说过,组合继承的缺点是无论什么时候都要调用2次父类型的构造函数,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类型构造函数的时候重写这些属性。因此,我们可以这样想,不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。

请看以下代码,实现了上面的想法:

function superClone(subType,superType){
var clone=objectClone(superType); //创建一个superType对象的副本,即创建继承了superType的新对象
clone.constructor=subType; //增强对象
subType.prototype=clone; //指定对象
}

本质上来说就是,使用寄生式继承来继承父类型的原型,然后将结果指定给子类型的原型。

完整的寄生组合式继承的代码如下:

function objectClone(object){
function F(){}
F.prototype=object;
return new F();
}
function superClone(subType,superType){
var clone=objectClone(superType);
clone.constructor=subType;
subType.prototype=clone;
}
function SuperType(name){
this.name=name;
this.friends=["tian","wang","xu"];
}
SuperType.prototype.sayname=function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age=age;
}
superClone(SubType,SuperType);
SubType.prototype.sayage=function(){
alert(this.age);
}
var instance1=new SubType("sun","24");
instance1.friends.push("fang");
alert(instance1.friends);//tian wang xu fang
var instance2=new SubType("liu","25");
//alert(instance2.friends);//tian wang xu


这个例子的高效率体现在它只调用了一次SuperType()构造函数,并且因此避免了在SubType.prototype上面创建不必要的多余的属性,与此同时,原型链还能保持不变。所以,寄生组合式继承是最理想的继承模式。

继承小结:

1、构造函数里存放的是那些特有属性和方法,每个对象都应该不同的。而原型对象里存放的应该是一些共享的属性和方法,每个对象都可以复用。

2、继承机制有很多种,但是本质都一样。那就是子类型都要拥有父类型的所有属性和方法,这样的过程就是继承。

3、原型链继承机制就是利用子类型的原型对象等于父类型的实例来实现的,这个过程实际上就是重写子类型的原型对象,在子类型的原型对象中拥有父类型的构造函数里的所有属性和方法,并且将父类型的原型对象链接起来了。

4、构造函数继承机制就是利用call()函数在子类型的构造函数中调用父类型的构造函数,实现继承。

5、组合继承机制就是综合以上的原型链机制和构造函数机制,原型链实现原型属性和方法的继承,构造函数实现对实例属性和方法的继承。

6、原型式继承机制就是创建一个已有对象的副本。

7、寄生式继承和原型式继承本质上是一样的,只不过它在创建对象副本以后使用同一的方法增强对象。

8、寄生组合继承的机制就是构造函数继承和寄生式继承的组合,其中这里的寄生式继承就是创建父类型的原型对象副本,然后把子类型的原型对象链接上去,这样子类型原型对象上就不会有父类型的实例属性和方法,避免了在子类型的原型对象上创建多余的不必要的属性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: