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

《编写高质量代码:改善JavaScript程序的188个建议》建议61:使用闭包跨域开发

关灯直达底部

闭包是指词法表示包括不必计算的变量的函数,闭包函数能够使用函数外定义的变量。闭包结构有以下两个比较鲜明的特性。

(1)封闭性

外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口。

(2)持久性

对于一般函数来说,在调用完毕之后,系统会自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在系统中,闭包中的数据依然存在,从而实现对数据的持久使用。例如:


function f(x){

var a=x;

var b=function{

return x;

}

return b;

}

var c=f(1);

alert(c);//1。调用闭包函数


在上面示例中,首先在函数f结构体内定义两个变量,分别存储参数和闭包结构,而闭包结构中寄存着参数值。当调用函数f之后,函数结构被注销,它的局部变量也随之被注销,因此变量a中存储的参数值也随之丢失。但由于变量b存储着闭包结构,因此闭包结构内部的参数值并没有被释放,在调用函数之后,依然能够从闭包结构中读取到参数值。

从结构上分析,闭包函数与普通函数没有什么不同,主要包含以下类型的标识符:

❑函数参数(形参变量)。

❑arguments属性。

❑局部变量。

❑内部函数名。

❑this(指代闭包函数自身)。

其中this和arguments是系统默认的函数标识符,不需要特别声明。这些标识符在闭包体内的优先级是(其中左侧优先级大于右侧):this→局部变量→形参→arguments→函数名。

下面以一个经典的闭包示例来演示上述抽象描述:


1 function f(x){//外部函数

2 var a=x;//外部函数的局部变量,并把参数值传递给它

3 var b=function{//内部函数

4 return a;//访问外部函数中的局部变量

5};

6 a++;//访问后,动态更新外部函数的变量

7 return b;//内部函数

8}

9 var c=f(5);//调用外部函数,并赋值

10 alert(c);//调用内部函数,返回外部函数更新后的值6


演示步骤说明如下:

第1步,程序预编译之后,从第9行开始解析执行,创建上下文环境,创建调用对象,把参数、局部变量、内部的函数转换为对象属性。

第2步,执行函数体内代码。在第6行执行局部变量a的递加运算,并把这个值传递给对象属性a,内部函数动态保持与局部变量a的联系,同时更新自己内部调用变量的值。

第3步,外部函数把内部函数返回给全局变量c,实现内部函数的定义,此时c完全继承了内部函数的所有结构和数据。

第4步,外部函数返回后(即返回值后调用完毕)会自动销毁,内部的结构、标识符和数据也随之丢失。

第5步,执行第10行代码命令,调用内部函数,此时返回的是外部函数返回时(销毁之前)保存的变量a所存储的最新数据值,即返回6。

如果没有闭包函数的作用,那么这种数据寄存和传递就无法得以实施。例如:


1 function f(x){

2 var a=x;

3 var b=a;//直接把局部变量的值传递给局部变量b

4 a++

5 return b;//局部变量b

6}

7 var c=f(5);

8 alert(c);//值为5


通过上面的示例可以很直观地看到,在没有闭包函数的辅助下,第8行代码执行后返回值并没有与外部函数的局部变量a最后更新的值保持一致。

闭包在程序开发中具有重要的价值。例如,使用闭包结构能够跟踪动态环境中数据的实时变化,并即时存储。


function f{

var a=1;

var b=function{

return a;

}

a++;

return b;

}

var c=f;

alert(c);//返回2,而不是1


在上面示例中,闭包中的变量a存储的值并不是对上面行变量a的值的简单复制,而是继续引用外部函数定义的局部变量a中的值,直到外部函数f调用返回。闭包不会因为外部函数环境的注销而消失,会始终存在。例如:


<script language=/"javascript/"type=/"text/javascript/">

function f{

var a=1;

b=function{

alert(a);

}

c=function{

a++;

}

d=function(x){

a=x;

}

}

</script>

<button onclick=/"f/">按钮1:(f)</button><br/>

<button onclick=/"b/">按钮2:(b=function{alert(a);})</button><br/>

<button onclick=/"c/">按钮3:(c=function{a++;})</button><br/>

<button onclick=/"d(100)/">按钮4:(d=function(x){a=x;})(100)</button><br/>


在上面示例中,在函数f中定义了3个闭包函数,它们分别指向并寄存局部变量a的值,并根据不同的操作动态跟踪变量a的值。当在浏览器中预览时,首先应该单击“按钮1”,调用函数f,生成3个闭包,3个闭包同时指向局部变量a的引用,因此,当函数f返回时,这3个闭包函数都没有被注销,变量a由于被闭包引用而继续存在。这时,如果直接单击“按钮2”和“按钮4”,那么会由于没有在系统中生成闭包结构,而弹出编译错误。单击“按钮3”将动态递增变量a的值,此时如果单击“按钮2”,则会弹出提示值2。如果单击“按钮4”,则向变量a传递值100,将动态改变闭包中寄存的值,此时如果单击“按钮2”,则会弹出提示值100。