下面的代码片断缩进目前还不完善,你也可以选择 [url=http://xiazai.jb51.net/200909/yuanma/javascript_closure_jb51.rar]下载pdf [/url]来阅读.
Contents
[list]
[*][url=http://www.1sucai.cn/article/20109.htm#id1]摘要[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#closure]什么是closure[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#execution-context]执行空间(执行上下文, Execution Context)[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#id2]closure的一些用法[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#id3]关于closure的效率[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#id4]应用建议[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#id5]结论[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#id6]参考资料[/url]
[/*][*][url=http://www.1sucai.cn/article/20109.htm#rst]本文的rst源码[/url] [/*][/list]
[url=http://www.1sucai.cn/article/20109.htm#id13]什么是closure[/url]
一种定义是:
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
我的理解是: [i]closure[/i] 是一个表达式(通常是一个函数), 这个表达式与一个 [b]环境[/b] 共享着一些自由变量, 而这个 [b]环境[/b] 则 [b]绑定[/b] 着那些自由变量(或者说 [b]结束[/b] 这个表达式, 这也是所谓[i]closure[/i] 的名字由来). 所谓的 [b]环境[/b] 就是一个更大的block, 所有的自由变量在这个 block 中 声明(有意义). 而 [b]绑定[/b] 也就是指这些自由变量的作用域就是这个环境.
举个简单的例子.
var flag = false; //调试开关
// env 既是所谓的环境
// 而inner就是所谓的表达式, name即是所谓的自由变量
function env() //整个env可以看作是一个closure
{
var name = "zhutao";
function inner()
{
return name + " is a student.";
}
return inner; //返回的是一个内部函数
}//closure结束
flag = true;
if (flag)
{
// 此处是最神奇的地方, 代码执行在此处, inner函数其实已经出了env的body,
// 而仍然能够被引用, 这就是所谓形成了一个 closure
var inner_func_ref = env(); // 这时候inner_func_ref引用的就是inner()函数对象
alert(inner_func_ref()); // zhutao is a student.
}
而在上面的例子中, 函数env就是所谓的定义中的 [b]环境[/b], 函数inner就是定义中所谓的 [b]表达式[/b], 而name即是所谓的 [b]自由变量[/b], [b]绑定[/b] 在env这个 [b]环境[/b] 中. env的结束也即closure的结束.
[b]而在javascript中,如果内部函数出了自己的所在的外部函数的body仍然能够引用,则会形成所谓的closure.[/b]
在具体了解closure之前,我们需要了解一些其它的知识.
[url=http://www.1sucai.cn/article/20109.htm#id14]执行空间(执行上下文, Execution Context)[/url]
在 [url=http://en.wikipedia.org/wiki/JavaScript]javascript[/url] 中,每行可执行的代码都具有一定的 [b]执行空间[/b], 如全局的执行空间, 函数的执行空间, 递归后的函数执行空间等. 而一个完整的 [url=http://en.wikipedia.org/wiki/JavaScript]javascript[/url] 执行过程,可以看作是有一个[b]执行空间栈[/b] ,不断地 进行 [b]执行空间[/b] 的变化(出栈,进栈).
这个是很重要的概念,这个概念的理解与本系列的将要完成的另一篇文章 [b]this关键字[/b] 的理解也是密切相关的.
详细解释请参考即将完成的 [b]this关键字[/b] 的博文.
执行空间可以理解为具有属性的对象集, 但是通常这些属性都不是可随意访问的, 而这些对象集为代码的执行 提供了一定的上下文(空间).
当执行到一个函数时, 会建立此函数的执行空间(所谓进栈), 执行结束了, 从此执行空间退出返回到原来的执行空间(所谓 的出栈),而js解释器在运行过程中一起维护着这样一个 [b]执行空间栈[/b] 来为不同的代码提供不同的执行空间.
那么执行空间与closure有什么关系?
简单地说,一定的执行空间对应着一定的closure, 只有位于同一个closure的方法才能访问同一closure的变量.
举个简单的例子:
// 关于context的例子
flag = true;
var tmpobj = {
name : "zhutao",
func : function(){
return "call by func " + this.name;
}
};
if (flag)
{
// 代码执行在此处时context还是global
alert(tmpobj.name);
alert(tmpobj.func()); //进入func的context
// 回到global的context
}
[url=http://www.1sucai.cn/article/20109.htm#id15]closure的一些用法[/url]
[b]当内部函数和自由变量位于同一closure时,可以随意访问,而声明顺序并不重要.[/b]
几个常用的例子:
//一些应用
flag = true;
function OuterFun()
{
var num = 100;
var printNum = function(){alert(num);} //此处引用的num是引用,而不是值,所以后面改变num,此处的num同样生效
num ++;
return printNum;
}
var myfunc = OuterFun();
myfunc(); //输出的是101,而不是100
//另一个例子,下面的例子,可以看到匿名函数(内部函数)先于外部函数变量的声明,但是仍然能够访问外部函数的变量
// 也就是说内部函数与外部函数的变量位于同一个closure, 所以可以访问
function SameClosure()
{
var iCanAccess = function(){alert(name);};
var name = "zhutao";
return iCanAccess;
}
var testSameClosure = SameClosure();
testSameClosure();// zhutao
// 另一个应用,关于module pattern, 这样可以实际所谓的 private, public等方法和变量
var module = (function Module(){
var privateVar = "zhutao is private"; // private
return {
publicGetPrivateVar : function(){
return privateVar;
}, // public method, 可以取所谓的private变量
publicVar : "I'm a public variable" // public variable
};
})();
if (flag)
{
alert(module.publicGetPrivateVar()); // zhutao is private
alert(module.publicVar); // I'm a public variable
alert(module.privateVar); // undefined
}
[url=http://www.1sucai.cn/article/20109.htm#id16]关于closure的效率[/url]
因为在closure的实际应用可能会多次去生成一个内部函数(匿名),所以存在可能的效率问题.(对象的建立,内存管理释放等).
所以,应该尽量减少内部函数的生成, 而使用函数的引用.
例如:
// 关于效率的例子
flag = false;
// 这样,每次调用Outer时会产生匿名函数的开销
function Outer(obj)
{
obj.fun = function(){
alert("I am " + this.name);
};
}
if (flag)
{
var obj = { name : "zhutao"};
Outer(obj);
obj.fun();
}
// 更好的处理方式
function Outer_better(obj)
{
obj.fun = showme; // 这样调用的只是函数的引用
}
function showme()
{
alert("I am " + this.name);
}
if (flag)
{
var obj2 = { name : "zhutao"};
Outer_better(obj2);
obj2.fun();
}
[url=http://www.1sucai.cn/article/20109.htm#id17]应用建议[/url]
Don't use closures unless you
really need closure semantics.
In most cases, nonnested
functions are the right way to go.
Eric Lippert, Microsoft
上面的论述是基于效率的考虑, 而 IE 4-6 在使用closure时可能会存在内存泄露的问题,参考 [url=http://www.jibbering.com/faq/faq_notes/closures.html]JavaScript Closures[/url] 中的相关部分.
而在某些场合,你可能必须要使用closure, 如 [b]循环问题[/b].
代码:
flag = true;
// 向body中生成一些链接,然后绑定事件
function addLink(num)
{
for(var i=0; i<num; i++)
{
var link = document.createElement('a');
link.innerHTML = "Link " + i;
link.onclick = function(){
alert(i);
};
document.body.appendChild(link);
}
} //可惜的是,当你点击每个链接时,输出的都是 Link 4
// 使用closure 可以解决这个问题
function addLink2(num)
{
for(var i=0; i<num; i++)
{
var link = document.createElement('a');
link.innerHTML = "Link" + i;
link.onclick = function(j){ //使用closure
return function(){
alert(j);
};//返回一个函数
}(i);//调用这个函数
document.body.appendChild(link);
}
}
window.onload = addLink(4);
window.onload = addLink2(4);
为什么会出现上面的这个问题?(事实在之前的的一个项目中,也遇到了相同的问题,但是当时还不懂closure, 也是一头雾水)
这是因为,对于addLink, 在退出addLink函数之前, i已经变成了4,所以无论后面的事件触发,输出的都是4.
但是后者,使用了closure.使得j引用了当前的循环中的i,所以对于每个后续触发事件,都会按照预期地得到相应的结果.
具体的讨论可见: [url=http://stackoverflow.com/questions/1451009/javascript-infamous-loop-problem]SO[/url]
这即是一个典型的closure应用场景, 而如果不使用, 就无法解决这个问题.
[url=http://www.1sucai.cn/article/20109.htm#id18]结论[/url]
下面这段摘抄自 [url=http://www.javascriptkit.com/javatutors/closures3.shtml]Summary of JavaScript closures[/url] :
[list=1]
[*]当你在一个函数中使用另一个函数时, 会产生一个closure
[/*][*]当你使用eval()时, 会产生一个closure.
[/*][*]最好认为closure总是在函数入口处产生,并且本地变量自动添加到closure中 [/*][/list]
其它的细节可参考上面的链接.
总之, 关于closure,你必须记住以下几点:
[list=1]
[*]closure就是提供了一种变量共享的机制(内部函数可以访问外部函数的变量)
[/*][*]注意closure可能引用的效率问题(如何避免,参见文中详述)
[/*][*]具体的应用场景要熟悉 [/*][/list]
上篇博文讲的是 [url=http://www.cnblogs.com/mindsbook/archive/2009/09/19/javascriptYouMustKnow1.html]prototype[/url], 下篇博文预计会讲 [b]this关键字[/b], 欢迎大家讨论和留言.
[url=http://www.1sucai.cn/article/20109.htm#id19]参考资料[/url]
[list=1]
[*][url=http://www.jibbering.com/faq/faq_notes/closures.html]JavaScript Closures[/url]
[/*][*][url=http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/]Explaining JavaScript Scope And Closures[/url]
[/*][*][url=http://www.javascriptkit.com/javatutors/closures.shtml]JavaScript Closures 101[/url]
[/*][*][url=http://www.javascriptkit.com/javatutors/closuresleak/index.shtml]JavaScript and memory leaks[/url]
[/*][*][url=http://james.padolsey.com/javascript/closures-in-javascript/]Closures in JavaScript[/url] [/*][/list]
[url=http://www.1sucai.cn/article/20109.htm#id20]本文的rst源码[/url]
本文的源码链接在 [url=http://sites.google.com/site/towerjoo/download/javascriptYouMustKnow-closure.rst?attredirects=0]这里[/url] .
本文中涉及的javascript代码可以在 [url=http://sites.google.com/site/towerjoo/download/closure.html?attredirects=0]这儿[/url] 下载.
你也可以选择 [url=http://xiazai.jb51.net/200909/yuanma/javascript_closure_jb51.rar]下载pdf [/url]来阅读.