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

《编写高质量代码:改善JavaScript程序的188个建议》建议101:比较类的构造和析构特性

关灯直达底部

构造和析构是创建和销毁对象的过程,它们是对象生命周期中的起点和终点,是最重要的环节。当一个对象诞生时,构造函数负责创建并初始化对象的内部环境,包括分配内存、创建内部对象和打开相关的外部资源等。析构函数负责关闭资源、释放内部的对象和已分配的内存。

在面向对象的编程中,构造和析构是类的两个重要特性。构造函数将在对象产生时调用,析构函数将在对象销毁时调用。调用的过程和实现方法由编译器完成,我们只需要记住它们调用的时间,因为它们的调用是自动完成的,不需要人工控制。

(1)构造函数

在JavaScript中,被new运算符调用的函数就是构造函数。构造函数被new运算符计算后,将返回实例对象,也就是所谓的对象初始化,即对象的诞生。调用构造函数的过程也是类实例化的过程。

如果构造函数有返回值,并且返回值是引用类型的,那么经过new运算符计算后,返回的不再是构造函数自身对应的实例对象,而是构造函数包含的返回值(即引用类型值)。


function F(x,y){

this.x=x;

this.y=y;

return;

}

var f=new F(1,2);

alert(f.constructor==F);//false,说明F不再是f的构造函数


在上面的示例中,返回值是一个空的数组,而不再是实例对象。原来构造函数的返回值覆盖了new运算符的运算结果,此时如果调用f的constructor属性,那么返回值是:


function Array{//被封闭的Array核心结构

[native code]

}


上面示例说明返回值是Array的实例,使用下面的代码可以检测出来:


alert(f.constructor==Array);//true,说明Array是f的构造函数


利用call和apply方法可以实现动态构造。例如,在下面这个示例中,构造函数A、B和C相互之间通过call方法关联在一起,当构造对象c时,将调用构造函数C,而在执行构造函数C时,会先调用构造函数B。在调用构造函数B之前,会自动调用构造函数C,从而实现动态构造对象的效果。这种多个构造函数相互关联在一起的情况称为多重构造。


function A(x){

this.x=x||0;

}

function B(x){

A.call(this,x);

this.a=[x];

}

function C(x){

B.call(this,x);

this.y=function{

return this.x;

}

}

var c=new C(3);

alert(c.y);//3


根据动态构造特性可以设计类的多态处理:


function F(x,y){//多态类型

function A(x,y){

this.add=function{

return x+/"/"+y;

}

}

function B(x,y){

this.add=function{

return x+y;

}

}

if(typeof x==/"string/"||typeof y==/"string/"){

A.call(this,x,y);

}

else{

B.call(this,x,y);

}

}

var f1=new F(3,4);

alert(f1.add);//调用对象方法add,返回数值7

var f2=new F(/"3/",/"4/");//实例化类F,传递字符串

alert(f2.add);//调用对象方法add,返回字符串34


(2)析构函数

析构是销毁对象的过程。由于JavaScript能够自动回收垃圾,不需要人工清除,所以当对象使用完毕时,JavaScript会调用对象回收程序来销毁内存中的对象,这个回收程序相当于一个析构函数。从文法角度来分析,JavaScript是不支持析构语法的。当然,我们也可以主动定义析构函数对对象进行清理。例如,先定义一个析构函数,该函数中包含一个析构方法,把该方法继承给任意对象,就可以调用它清除对象内部所有成员了。


function D{//析构函数

}

D.prototype={

d:function{//析构方法

for(var i in this){

if(this[i]instanceof D){

this[i].d;

}

this[i]=null;//清除成员

}

}

}

function F{

this.x=1;

this.y=function{

alert(2);

}

}

F.prototype=new D;//绑定析构函数,继承析构方法

var f=new F;//实例化试验函数

f.d;//调用析构方法

alert(f.x);//null,说明属性已经被注销

f.y//编译错误,说明方法已不存在


构造和析构有一个顺序问题。在其他强类型语言中,构造是从基类开始按继承的层次顺序进行的,析构的时候顺序正好相反。这样处理是因为子类可能在构造函数中使用父类的成员变量,如果父类还没有创建,那么就会有问题。而在析构的时候,如果父类先析构,也会出现这样的问题。JavaScript对此没有严格的要求,但它遵循从下到上的顺序进行构造,而析构则没有这方面的要求,只要对象没有成员引用或对象引用即可进行析构。