JavaScript的变量作用域是通过函数来维护的。举个例子,对于函数:

function add(a,b){ return a+b; }

而言,当使用不同的参数(不带参数的函数同样如此)调用它时:

var sum1 = add(1,2);
var sum2 = add(3,4);

每次调用都会通过创建一个新的调用对象维护一个新的函数作用域,从而保证了sum1和sum2分别取得相应的 值3和7。

而闭包的原理,也是如此。下面举两个例子,一个是因为闭包导致了问题,而另一个则利用闭包巧妙地通过函数的作用域绑定参数。这两个例子相关的HTML标记片断如下:

<a href=”#” id=”closureExample”>利用闭包的例子(0.5秒后您会看到提示)</a>
<a href=”#” id=”closureExample2″>由于闭包导致问题的例子1</a>
<a href=”#” id=”closureExample3″>由于闭包导致问题的例子2</a>
<a href=”#” id=”closureExample4″>由于闭包导致问题的例子3</a>


例一:因遭遇闭包而导致问题

上面标记片断中有4个<a>元素,假设要给后三个指定事件处理程序,使它们在用户单击时报告自己在页面中的顺序,比如:当用户单击第2个链接时,报告“您单击的是第2个链接”。

为此,如果编写下列为后三个链接添加事件处理程序的函数:

function badExample() { for (var i=2 ; i<=4 ; i++ ) { var el = document.getElementById('closureExample' + i); el.onclick = function(){ alert('您单击的是第' + i + '个链接'); } } }

然后,在页面载入完成后(不然可能会报错)调用该函数:

window.onload = function(){ badExample(); }

此时单击后3个链接,会看到警告框中显示什么信息呢?——全都是“您单击的是第5个链接”。为什么?

因为在badExample()函数中指定给el.onclick的事件处理程序——也就是那个匿名函数是在badExample()函数运行完成后(用户单击链接时)才被调用的。而调用时,需要对变量i求值,解析程序首先会在事件处理程序内部查找,但没有定义。然后,又到外部作用域,即badExample()函数中查找,此时有定义,但i的值是5(只有i大于5才会停止执行for循环)。因此,就会取得该值——这正是闭包(匿名函数)要使用其外部函数(badExample)作用域中变量的结果。而且,这也是由于匿名函数本身无法传递参数(故而无法维护自己的作用域)造成的。这个例子的问题怎么解决呢?大家自己想一想。

例二:利用闭包绑定参数

还是上面的HTML片段,我们要在用户单击第一个链接时延时弹出一个警告框——注意延时——怎么实现?答案是使用setTimeout()函数,这个函数会在指定的毫秒数之后调用一个函数,如:

setTimeout(someFunc,500);

但问题是,无法给其中的someFunc函数传递参数。而使用闭包则可以轻松解决这个问题:

function goodExample(i) { return function(){alert(i);}; }

函数goodExample用来返回一个匿名函数(闭包)。而我们可以通过为它传递参数来使返回的匿名函数绑定该参数,如:

var good = goodExample('这个参数是通过闭包绑定的');

而此时,就可以将绑定了参数的good函数传递给setTimeout()实现延时警告了:

setTimeout(good,500) //此时good中已经绑定了参数

最后,与为第一个链接指定事件处理程序结合起来,完整的代码就是:

window.onload = function(){ var el = document.getElementById('closureExample'); if (el){ var good = goodExample('这个参数是由闭包绑定的'); el.onclick = function(){ setTimeout(good,500); } } }



朋友们的留言

  1. wanghailong | 12月 13th, 2007 at 17:38

    不明白你的第二个例子的目的是什么,不用闭包也可以实现这个要求啊,难道只是为了闭包而闭包??

    Reply to this comment
  2. 琳琳的小狗 | 12月 17th, 2007 at 10:28

    呵呵,第二个例子,是Prototype库的Funciton#curry和Function#bind的原理,至于有什么用,写多了js的人都能明白,多说无益……

    Reply to this comment
  3. admin | 12月 17th, 2007 at 11:16

    :) 简单说一句。第二个例子中的good函数会因每次调用goodExample时传递的参数不同而绑定不同的参数。而这一点如果使用普通函数(非闭包或非嵌套函数)是很难实现的。
    可以参考《Pro JavaScript》一书或者我翻译的文章“理解JavaScript闭包

    Reply to this comment
  4. admin | 12月 26th, 2007 at 11:30

    不错,不通过闭包的确能实现对参数一次性的绑定(硬编码方式)。但对于在循环中动态绑定参数而言,如果不借助闭包,则几乎是不可能实现的——这种情况常见于实现动画效果。其中涉及到使用setTimeout()根据开发者传入的参数动态设置计时(或延时)循环。
    对此问题,请参考《Pro JavaScript》P27“Closures”一节和P152“Slide In”一节。

    Reply to this comment
  5. yangedie | 01月 23rd, 2008 at 17:24

    不好意思,想请问第一个例子怎么解决,我这样行不行?
    function badExample() {
    var nums=new Array(4);
    nums["closureExample"]=1;
    nums["closureExample2"]=2;
    nums["closureExample3"]=3;
    nums["closureExample4"]=4;
    for (var i=2 ; i

    Reply to this comment
  6. yangedie | 01月 23rd, 2008 at 17:27

    哦,被截掉了一半,for循环是这样的:
    for (var i=2 ; i

    Reply to this comment
  7. admin | 01月 23rd, 2008 at 21:52

    To:yangedie
    写完了吗?:)

    Reply to this comment
  8. yangedie | 01月 24th, 2008 at 00:19

    for (var i=2 ; i

    Reply to this comment
  9. yangedie | 01月 24th, 2008 at 00:21

    奇怪,总是被截掉了,其他都是一样
    el.onclick = function(){
    alert(“您单击的是第” + nums[this.id] + “个链接”);
    }

    Reply to this comment
  10. admin | 01月 24th, 2008 at 11:41

    TO:yangedie

    你的想法很不错。我试了一下,也能正常运行。
    不过,还有一个类似的方案:

    function badExample() {
    function addEvent(el,k){
    el.onclick = function(){
    alert(‘您单击的是第’ + k + ‘个链接’);
    }
    }
    for (var i=2 ; i<=4 ; i++ ) {
    var el = document.getElementById(‘closureExample’ + i);
    addEvent(el,i);
    }
    }

    Reply to this comment
  11. yangedie | 01月 24th, 2008 at 15:30

    谢谢博主,看来我还是对闭包不是很理解才做出我的那个方法

    Reply to this comment
  12. admin | 01月 24th, 2008 at 18:20

    哈哈,客气了。

    Reply to this comment
  13. Jason.Chen | 03月 11th, 2008 at 15:04

    匿名函数无法传递参数?做出这样的结论,是因为你对JS函数的理解还不到位,我改了你的例子
    function goodExample() {
    return function(i){alert(i);};
    }
    window.onload = function(){
    document.getElementById(“closureExample”).onclick =function(){setTimeout(‘goodExample()(“aaaa”)’,500);}
    }
    实际上,你的第一句话“JavaScript的变量作用域是通过函数来维护的”也不正确。

    Reply to this comment
  14. Zehee | 07月 2nd, 2008 at 20:58

    第一个例子,瞎写了一下。
    function badExample(){
    for(var i=1;i

    Reply to this comment
  15. Zehee | 07月 2nd, 2008 at 21:10

    function badExample(){
    for(var i=1;i小于=4;i++){
    var el = new Array()
    el[i] = document.getElementById(“closureExample” + i);
    el[i].index = i;
    el[i].onclick = function(){
    alert(“您点击的是第” + this.index+ “个链接!”)
    }
    }
    }

    window.onload = function(){
    badExample();
    }

    Reply to this comment
  16. yushih | 07月 25th, 2008 at 11:03

    function badExample() {
    for (var i=2 ; i

    Reply to this comment
  17. yushih | 07月 25th, 2008 at 11:04

    怎么评论会被截掉呢?
    function badExample() {
    for (var i=2 ; i

    Reply to this comment
  18. yushih | 07月 25th, 2008 at 11:05

    算了,没法评论。谢谢博主提出这个有趣的问题。

    Reply to this comment
  19. librajt | 11月 30th, 2010 at 18:04

    addEvent(el,i);
    请问,这种解决方法有什么特殊之处吗?
    朋友说,这只是参数传递。是这样吗?
    JS初学者,期待回复:D

    Reply to this comment
  20. 小呆 | 07月 29th, 2011 at 16:20

    再加一层匿名函数也可以吧
    function badExample() {
    for (var i=2 ; i小于=4 ; i++ ) {
    (function(){
    var count = i;
    var el = document.getElementById(‘closureExample’ + i);
    el.onclick = function(){
    alert(‘您单击的是第’ +count + ‘个链接’);
    }
    })();
    }
    }

    Reply to this comment
  21. 12 | 09月 9th, 2011 at 14:45

    我测试下这个编辑

    var lists = document.getElementsByTagName(‘li’);
    for (var i = 0, len = lists.length; i < len; i++) {
    lists[i].onmouseover = function () {
    alert(i);
    };
    }

    function fun(num) {
    if (num <= 1)
    return 1;
    else
    return num * arguments.callee(num – 1);
    };
    var f = fun;
    alert(f == fun); //true
    fun = null;
    alert(f == fun); //false

    Reply to this comment
  22. kam85 | 09月 14th, 2011 at 09:42

    第一个例子再加个嵌套就可以了。
    function example(){
    for(var i=2;i<5;i++){
    var el = document.getElementById("example" + i);
    el.onclick = (function(){
    var j = i;
    return function(){
    alert("第" + j + "个链接!");
    }
    })();
    }
    }

    Reply to this comment
  23. bridal wedding dresses | 12月 14th, 2011 at 10:25

    nice post , i like it , thanks for sharing it . have a nice day.

    Reply to this comment
  24. one shoulder wedding dresses | 01月 5th, 2012 at 13:07

    good post , i like it , thanks for sharing it . have a nice day .

    Reply to this comment

我来说两句儿

可以在留言中使用以下标签 :<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam Protection by WP-SpamFree