构造和析构是创建和销毁对象的过程,它们是对象生命周期中的起点和终点,是最重要的环节。当一个对象诞生时,构造函数负责创建并初始化对象的内部环境,包括分配内存、创建内部对象和打开相关的外部资源等。析构函数负责关闭资源、释放内部的对象和已分配的内存。
在面向对象的编程中,构造和析构是类的两个重要特性。构造函数将在对象产生时调用,析构函数将在对象销毁时调用。调用的过程和实现方法由编译器完成,我们只需要记住它们调用的时间,因为它们的调用是自动完成的,不需要人工控制。
(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对此没有严格的要求,但它遵循从下到上的顺序进行构造,而析构则没有这方面的要求,只要对象没有成员引用或对象引用即可进行析构。