首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》建议93:推荐使用类继承

关灯直达底部

类继承也称为构造函数继承,还称为对象模拟法。其表现形式:在子类中执行父类的构造函数。其实现本质:构造函数也是函数,与普通函数相比,它只不过是一种特殊结构的函数而已。可以将一个构造函数(如A)的方法赋值为另一个构造函数(如B),然后调用该方法,使构造函数A在构造函数B内部被执行,这时构造函数B就拥有在构造函数A中定义的属性和方法,这就是所谓B类继承A类。下面看一个示例:


function A(x){//构造函数A

this.x=x;

this.say=function{

alert(this.x);

}

}

function B(x,y){//构造函数B

this.m=A;//把构造函数A作为一个普通函数引用给临时方法m

this.m(x);//把当前构造函数参数x作为值传递给构造函数A,并执行

delete this.m;//清除临时方法

this.y=y;

this.call=function{

alert(this.y);

}

}

var a=new A(1);

var b=new B(2,3);

a.say;//调用实例化A的方法say,返回1

b.say;//在B类中调用A类的方法say,返回2,说明继承成功

b.call;//调用实例化B的方法call,返回3


构造函数能够使用this关键字为所有属性和方法赋值。在默认情况下,关键字this引用的是构造函数当前创建的对象。不过在这个方法中,this不是指向当前正在使用的实例对象,而是调用构造函数的方法所属的对象,即构造函数B。此时,构造函数A已经不是构造函数了,而被视为一个普通可执行函数。

上面的示例演示了类继承的实现基础。实际上,在复杂的编程中,是不会使用上面方法来定义类继承的,因为它的设计模式太随意,缺乏严密性。严谨的设计模式应该考虑到各种可能存在的情况和类继承关系中的相互耦合性。为了更直观地说明,先看一个示例:


function A(x){//构造函数A

this.x=x;

}

A.prototype.getx=function{

return this.x;

}


在上面的代码中,先创建一个构造函数,它相当于一个类,类名是构造函数的名称A。在结构体内使用this关键字创建本地属性x。方法getx被放在类的原型对象中成为公共方法。然后,借助new运算符调用构造函数,返回的是新创建的对象实例:


var a1=new A(1);


最后,对象a1就可以继承类A的本地属性x,也有人称之为实例属性,当然还可以访问类A的原型方法getx:


alert(a1.x);//继承类A的属性x

alert(a1.getx);//引用类A的方法getx


上面的代码是一个简单的类的演示。现在,创建一个类B,让其继承类A,实现的代码如下:


function B(x,y){//构造函数B

this.y=y;

A.call(this,x);//在构造函数B中调用超类A,实现绑定

}

B.prototype=new A;//设置原型链,建立继承关系

B.prototype.constructor=B;//恢复B的原型对象的构造函数为B

B.prototype.gety=function{

return this.y;

}


在构造函数B的结构体内,使用函数call调用构造函数A,把B的参数x传递给调用函数。让B能够继承A的所有属性和方法,即执行“A.call(this,x);”语句行。在构造函数A和B之间建立原型链,即执行“B.prototype=new A;”语句行。恢复B的原型对象的构造函数,即执行“B.prototype.constructor=B;”语句行。当定义构造函数时,其原型对象(prototype属性值)默认是一个Object类型的一个实例,其构造器(constructor属性值)会被默认设置为该构造函数本身。如果改动prototype属性值,使其指向另一个对象,那么新对象就不会拥有原来的constructor属性值,所以必须重新设置constructor属性值。

此时,就可以在子类B的实例对象中调用超类A的属性和方法了。


var f2=new B(10,20);

alert(f2.getx);//10

alert(f2.gety);//20


最后,看一个更复杂的多重继承的实例。


//基类A

function A(x){

this.getl=function{

return x;

}

}

A.prototype.has=function{

return!(this.getl==0);

}

//超类B

function B{

var a=;

a=Array.apply(a,arguments);

A.call(this,a.length);

this.add=function{

return a.push.apply(a,arguments);

}

this.geta=function{

return a;

}

}

B.prototype=new A;//建立原型链

B.prototype.constructor=B;//恢复构造器

B.prototype.str=function{

return this.geta.toString;

}

//子类C

function C{

B.apply(this,arguments);//在当前对象中调用B类

this.sort=function{

var a=this.geta;

a.sort.apply(a,arguments);

}

}

C.prototype=new B;//建立原型链

C.prototype.constructor=C;//恢复C类原型对象的构造器

//超类B的实例继承类A的成员

var b=new B(1,2,3,4);

alert(b.getl);//4

alert(b.has);//true

//子类C的实例继承类B和类A的成员

var c=new C(30,10,20,40);

c.add(6,5);

alert(c.geta)//数组30,10,20,40,6,5

c.sort//排序数组

alert(c.geta)//数组10,20,30,40,5,6

alert(c.getl)//4

alert(c.has);//true

alert(c.str);//10,20,30,40,5,6


在上面的示例代码中,设计类C继承类B,而类B又继承了类A。A、B、C三个类之间的继承关系是通过在子类中调用父类的构造函数来维护的。例如,在C类中添加“B.apply(this,arguments);”语句,该行语句能够在B类中调用A类,并且把B的参数传递给A,从而使B类拥有A类的所有成员。同理,在B类中添加“A.call(this,a.length);”语句,该行语句把B类的参数长度作为值传递给A类,并进行调用,从而实现B类拥有A类的所有成员。

从继承关系上看,B类继承了A类的本地方法getl。为了确保B类还能够继承A类的原型方法,还需要为它们建立原型链,从而实现原型对象的继承关系,方法是添加语句行“B.prototype=new A;”。同理,在C类中添加语句行“C.prototype=new B;”,这样就可以把A、B和C三个类通过原型链连在一起,从而实现子类能够继承超类成员,甚至还可以继承基类成员。这里的成员主要指类的原型对象包含的成员,当然它们之间也可以通过相互调用来实现对本地成员的继承关系。

注意原型继承中的先后顺序,在为B类的原型指定A类的实例前,不能再为其定义任何原型属性或方法,否则就会被覆盖。如果要扩展原型方法,就只有在进行原型绑定之后,再定义扩展方法。