闭包意味着子函数在其父函数结束后,仍能调用其父函数的变量。
先看以下两个事例,来了解一下闭包的创建:
<script type="text/javascript"> //ex01 var mess = "我是最原始的信息"; setTimeout(function(){document.writeln(mess+"<br/>")},1000); mess = "我是修改后的信息"; setTimeout(function(){document.writeln(mess+"<br/>")},1000); //ex02 function delayedShow(message,time){ setTimeout(function(){ document.writeln(message+"<br/>"); },1000) } var message = "我是最原始的信息"; delayedShow(message,1000); message = "我是修改后的信息"; delayedShow(message,1000); </script>
ex01输出的结果为:我是修改后的信息 我是修改后的信息
ex02输出的结果为: 我是最原始的信息 我是修改后的信息
我们来分析一下,第一个事例比较简单,第一次调用函数之后1秒钟后进行setTimeout回调,setTimeout回调的function始终指向一个全局变量,mess。在setTimeout的方法还未被执行之前,mess已经被改变,所以两次输出的都是 “我是修改后的信息”。第二个实例就用到了闭包,当我们调用delayedShow的时候,实际上已经得到了setTimeout的回调函数的引用,让其在1秒钟后执行。在一个函数的子函数被外部引用时,就产生了闭包。也就是说,在一个函数的子函数被外部引用时,就产生了一个环境,在这个环境里面,所使用的变量都是独立的。在第一次调用delayedShow(message,1000)时,产生了一个环境,就是闭包结构,在这个环境里面,message的值为“我是最原始的信息”。第二次调用delayedShow(message,1000)时,又产生了一个环境,在这个环境里面message的值为“我是修改后的信息”。举个例子来说,一个函数嵌套一个子函数可以相当于一个面包屋(父函数)里面有一个面包机(子函数)。由于函数可以被重复调用,所以我们姑且假设这个面包屋可以被无限克隆。当外部派一个奶油面包师傅(参数)来经营这个面包屋的时候,这个面包师傅就克隆了一个面包屋,这个面包屋只卖奶油面包,掌管面包机的是奶油面包师傅。当外部派一个肉松面包师傅来经营这个面包屋的时候,这个肉松面包也克隆了一个面包屋,这个面包屋的内部结构跟奶油面包屋是一模一样的,只不过它只卖肉松面包,掌管面包机的是肉松面包师傅。依些内推。这里产生的每一个面包屋都叫一个闭包。
好了,我们再来看一个例子。
在我们平时的编码时,常常会碰到这么一个问题。先看代码,这段代码的本意是要点击一个按钮时分别弹出提示框,告诉用户其点击了哪个按钮
<input type="button" value="Button 1" id="btn1"> <input type="button" value="Button 2" id="btn2"> <input type="button" value="Button 3" id="btn3"> <script type="text/javascript"> function createEventHandlers(){ var btn; for(var i=1; i <= 3; i++){ btn = document.getElementById('btn' + i); btn.onclick = function(){ alert('Clicked button #' + i); } } } createEventHandlers(); </script>
这个代码在输出时,会弹出三个 Clicked button#3。代码似乎写得没错,一个简单的for循环。好吧,我们再看一个代码:
function createEventHandlers(){ var btn; for(var i=1; i <= 3; i++){ btn = document.getElementById('btn' + i); btn.onclick = createOneHandler(i); } } function createOneHandler(number){ return function() { alert('Clicked button #' + number); } }
咦,这段代码输出时分别会弹出Clicked button #1 Clicked button #2 Clicked button #3,为什么把函数独立出来就对了呢?
我们来改造一下上面两段代码,把for循环解开来写
<script type="text/javascript"> //ex01 var i = 1; click1 = function() { document.writeln("click button"+i); } i = 2; click2 = function(){ document.writeln("click button"+i); } i = 3; click3 = function (){ document.writeln("click button"+i); } //点击第一个按钮 click1(); //点击第二个按钮 click2(); //点击第三个按钮 click3(); //ex02 click5=function(k){ return function(){ document.writeln("click button"+k); } } var j = 1; bclick1=click5(j); j = 2; bclick2=click5(j); j = 3; bclick3=click5(j); bclick1(); bclick2(); bclick3(); </script>
看到这个,明白了吧,跟第一个例子一样,第一个写法中呢,函数依赖的是一个全局变量。第二个例子呢,我们使用闭包保存了变量。
另外,还有一个js编程中常常会碰到的问题也可以依靠闭包来解决。在编写js代码时,我们常常会将许多变量放在全局区域,这是一个糟糕的实践。原因呢,当然是因为它会干涉到其它代码库的运行,从而产生一些令人迷惑的问题。使用一个自动生效的匿名函数闭包可以从本质上将全局变量隐藏起来,不让其干涉到其它的库。
看例子:
// 利用包装器来创建一个匿名函数 (function(){ // 在这变量在这个包装器内是一个全局变量 var msg = "Thanks for visiting!"; // 将一个方法绑定到全局变量上 window.onunload = function(){ // 这里使用“被隐藏”起来的全局变量 alert( msg ); }; // 关闭匿名函数并且执行它 })();
这样子,我们就可以把利用到的变量与其它库隔离开来。(function(){})()中最后面的()是代表立即执行这个function。我们可以把代码这样改写,可以理解()的意义。
var f = function(){ var msg = "Thanks for visiting!"; window.onunload = function(){ alert( msg ); }; }; f();
个人博客地址:http://www.heymemory.com/blog/9/