JavaScript闭包浅谈
闭包是JavaScript的复杂特性之一,搞清楚它有益于JS复杂程序的编写和排错。什么是闭包呢?让我们先看看《jQuery基础教程》一书中对闭包的描述:
“当内部函数在定义它的作用于的外部被引用时,就创建了该内部函数的一个闭包。在这种情况下,我们称布什内部函数局部变量的变量为自由变量,称外部函数的调用环境为封闭闭包的环境。从本质上讲,如果内部函数引用了位于外部函数中的变量,相当于授权该变量能够被延迟使用。因此,当外部函数调用完成后,这些变量的内存不会被释放,因为闭包仍然需要使用它们。”
这段话解释得很清楚了。如果再简短一些,用我自己的话说,闭包=封闭+打包,它的作用是将内部函数及其使用到的外部变量(内部函数的作用环境)封闭在一起打包使用。具体过程请看下面的程序:
执行以上程序,很容易就发觉,内部函数inner和外部函数定义的局部变量i被封闭并打包在一起了,就是闭包。这种方式和Java中向匿名类传递参数需要用final指定外部变量的做法异曲同工,都是改变局部变量的生存周期和作用范围以配合特定函数的使用而已,不同的是Java在明JS在暗。
如果创建了内部函数的多个副本,那么被打包的外部函数中的变量也会产生互不影响的多个副本,从下面的程序可以清晰的看出来:
明显,pointe1和pointer2指向的是inner的两个副本,它们各自有不同的闭包环境,因此使用到的i也是从属于各自闭包环境的两个不同变量,它们地址,值都各不相同,相同的只有名字而已,就好像两个一般函数中都有变量i一样。
以上程序的完整代码请见: http://www.blogjava.net/Files/heyang/closure20090823214249.rar
JS闭包的这个特性对动态创建的程序特别有效,如下图中“删除”按钮的创建,就会用到闭包。
请看代码:
deleteBtn.onclick=function(){deleteRow(code);};这句代码为什么一定要用一个函数体包容一下呢?我们知道deleteRow这个函数的作用是删除掉一个表格行,传入的参数是行的id,而每行的id是不同的,程序中的id值来自于局部变量code,我们不希望它创建完按钮后值发生变化而是希望它运行时的值和每个按钮的点击相应函数绑定在一起,用JS的闭包正好完成了这一点。
以上代码的完整程序请见: http://www.blogjava.net/Files/heyang/StockTable.rar
在 http://www.blogjava.net/heyang/archive/2009/08/21/292106.html 中的程序里也使用到了闭包,以下的程序:
当然,不要红字部分的function也是一种闭包,只是打包封闭的环境错误了而已.
以上代码的完整程序请见: http://www.blogjava.net/Files/heyang/JSCSSPopupmenu20090821151741.rar
“当内部函数在定义它的作用于的外部被引用时,就创建了该内部函数的一个闭包。在这种情况下,我们称布什内部函数局部变量的变量为自由变量,称外部函数的调用环境为封闭闭包的环境。从本质上讲,如果内部函数引用了位于外部函数中的变量,相当于授权该变量能够被延迟使用。因此,当外部函数调用完成后,这些变量的内存不会被释放,因为闭包仍然需要使用它们。”
这段话解释得很清楚了。如果再简短一些,用我自己的话说,闭包=封闭+打包,它的作用是将内部函数及其使用到的外部变量(内部函数的作用环境)封闭在一起打包使用。具体过程请看下面的程序:
<
SCRIPT LANGUAGE
=
"
JavaScript
"
>
<!--
// 定义两个引用
var pointer1;
var pointer2;
// 外部函数定义
function outer(){
// 外部函数的局部变量
var i = 0 ;
// 在外部函数内部定义的内部函数,使用了外部函数的局部变量i
function inner(){
alert(i);
i ++ ;
}
// 将两个引用指向内部函数inner
pointer1 = inner;
pointer2 = inner;
}
outer();
alert( " 通过引用调用内部函数开始 " );
pointer1(); // 弹出0
pointer1(); // 弹出1
pointer2(); // 弹出2
pointer2(); // 弹出3
// -->
</ SCRIPT >
<!--
// 定义两个引用
var pointer1;
var pointer2;
// 外部函数定义
function outer(){
// 外部函数的局部变量
var i = 0 ;
// 在外部函数内部定义的内部函数,使用了外部函数的局部变量i
function inner(){
alert(i);
i ++ ;
}
// 将两个引用指向内部函数inner
pointer1 = inner;
pointer2 = inner;
}
outer();
alert( " 通过引用调用内部函数开始 " );
pointer1(); // 弹出0
pointer1(); // 弹出1
pointer2(); // 弹出2
pointer2(); // 弹出3
// -->
</ SCRIPT >
执行以上程序,很容易就发觉,内部函数inner和外部函数定义的局部变量i被封闭并打包在一起了,就是闭包。这种方式和Java中向匿名类传递参数需要用final指定外部变量的做法异曲同工,都是改变局部变量的生存周期和作用范围以配合特定函数的使用而已,不同的是Java在明JS在暗。
如果创建了内部函数的多个副本,那么被打包的外部函数中的变量也会产生互不影响的多个副本,从下面的程序可以清晰的看出来:
<
SCRIPT LANGUAGE
=
"
JavaScript
"
>
<!--
// 定义两个引用
var pointer1;
var pointer2;
// 外部函数定义
function outer(){
// 外部函数的局部变量
var i = 0 ;
// 在外部函数内部定义的内部函数,使用了外部函数的局部变量i
function inner(){
alert(i);
i ++ ;
}
// 返回内部函数的引用
return inner;
}
// 将引用指向内部函数
pointer1 = outer();
pointer2 = outer();
alert( " 通过引用调用内部函数开始 " );
pointer1(); // 弹出0
pointer1(); // 弹出1
pointer2(); // 弹出0
pointer2(); // 弹出1
// -->
</ SCRIPT >
<!--
// 定义两个引用
var pointer1;
var pointer2;
// 外部函数定义
function outer(){
// 外部函数的局部变量
var i = 0 ;
// 在外部函数内部定义的内部函数,使用了外部函数的局部变量i
function inner(){
alert(i);
i ++ ;
}
// 返回内部函数的引用
return inner;
}
// 将引用指向内部函数
pointer1 = outer();
pointer2 = outer();
alert( " 通过引用调用内部函数开始 " );
pointer1(); // 弹出0
pointer1(); // 弹出1
pointer2(); // 弹出0
pointer2(); // 弹出1
// -->
</ SCRIPT >
明显,pointe1和pointer2指向的是inner的两个副本,它们各自有不同的闭包环境,因此使用到的i也是从属于各自闭包环境的两个不同变量,它们地址,值都各不相同,相同的只有名字而已,就好像两个一般函数中都有变量i一样。
以上程序的完整代码请见: http://www.blogjava.net/Files/heyang/closure20090823214249.rar
JS闭包的这个特性对动态创建的程序特别有效,如下图中“删除”按钮的创建,就会用到闭包。
请看代码:
//
deleteImgCell
var deleteBtn = document.createElement( " input " );
deleteBtn.setAttribute( " type " , " button " );
deleteBtn.setAttribute( " value " , " 删除 " );
deleteBtn.onclick = function (){deleteRow(code);};// 这里用到了闭包
var deleteBtnCell = document.createElement( " td " );
deleteBtnCell.appendChild(deleteBtn);
var deleteBtn = document.createElement( " input " );
deleteBtn.setAttribute( " type " , " button " );
deleteBtn.setAttribute( " value " , " 删除 " );
deleteBtn.onclick = function (){deleteRow(code);};// 这里用到了闭包
var deleteBtnCell = document.createElement( " td " );
deleteBtnCell.appendChild(deleteBtn);
deleteBtn.onclick=function(){deleteRow(code);};这句代码为什么一定要用一个函数体包容一下呢?我们知道deleteRow这个函数的作用是删除掉一个表格行,传入的参数是行的id,而每行的id是不同的,程序中的id值来自于局部变量code,我们不希望它创建完按钮后值发生变化而是希望它运行时的值和每个按钮的点击相应函数绑定在一起,用JS的闭包正好完成了这一点。
以上代码的完整程序请见: http://www.blogjava.net/Files/heyang/StockTable.rar
在 http://www.blogjava.net/heyang/archive/2009/08/21/292106.html 中的程序里也使用到了闭包,以下的程序:
window.onload
=
function
(){
var menubar = $( " menubar " );
for ( var i = 0 ;i < menubar.childNodes.length;i ++ ){
new function (){// 这里使用了闭包,将局部变量和事件响应函数打包封闭在了一起.
var li = menubar.childNodes[i];
var subul = li.childNodes[ 2 ];
li.attachEvent('onmouseover',
function (){
subul.style.display = " block " ;
}
);
li.attachEvent('onmouseout',
function (){
subul.style.display = " none " ;
}
);
}
}
}
var menubar = $( " menubar " );
for ( var i = 0 ;i < menubar.childNodes.length;i ++ ){
new function (){// 这里使用了闭包,将局部变量和事件响应函数打包封闭在了一起.
var li = menubar.childNodes[i];
var subul = li.childNodes[ 2 ];
li.attachEvent('onmouseover',
function (){
subul.style.display = " block " ;
}
);
li.attachEvent('onmouseout',
function (){
subul.style.display = " none " ;
}
);
}
}
}
当然,不要红字部分的function也是一种闭包,只是打包封闭的环境错误了而已.
以上代码的完整程序请见: http://www.blogjava.net/Files/heyang/JSCSSPopupmenu20090821151741.rar