javascript中的闭包

记得刚刚接触javascript时,自己对javascript的闭包也很迷惑。有一次,一位同事遇到了下面的问题,很多同学一定也遇到过类似的问题。

问题描述

我们要给四个有编号的按钮绑定点击事件。当点击某个按钮时,弹出此按钮的编号。

    <button>0</button> 
    <button>1</button> 
    <button>2</button> 
    <button>3</button> 
    var button = document.getElementsByTagName("button");

    var addEventListener = function(nodes){
        var i;

        for(i = 0; i < nodes.length; i++){
            nodes[i].onclick = function(e){
                alert(i);
            }
        }
    }

    addEventListener(button);

大家可以到这里运行它.

结果是什么?结果是,无论我们点击哪个按钮,都会弹出按钮的个数,是不是似曾相识?为什么呢?

一个闭包的例子

    function foo(){
        var a = 10;

        function bar(){
            a *= 2;
            return a;
        }

        return bar;
    }

    var baz = foo();
    alert(baz()); //20
    alert(baz()); //40
    alert(baz()); //80

大家可以到这里运行它.

在javascript中,只有函数具有作用域。也就是说,在一个函数内部声明的变量在函数外部无法访问。

在上述代码中,所返回的对bar函数的引用被赋给变量baz.这个函数现在是在foo外部被调用,但它依然能够访问a。这是因为javascript中的作用域是词法性的。函数是运行在定义它们的作用域中(本例中是foo内部的作用域),而不是运行在调用它们的作用域中。只要bar被定义在foo中,它就能访问在foo中定义的所有变量,即使foo的执行已经结束。

上面就是一个闭包的例子。我想,我不能只用一句话解释什么是闭包,或者定义什么是闭包,只能通过这个例子了。

再看第一个例子

有了上面对闭包的解释,再看一下那些按钮当被点击的时候为什么总是弹出按钮的个数呢?

我们可以这样想:for循环中,我们只是为所有按钮绑定了点击事件,当它们被点击时会弹出i的当前值。但是,当循环结束的时候,i的值已经是按钮的个数了。这时,当我们再点击按钮的时候,程序要弹出i的值,也当然就是按钮的个数了,就像上面闭包的那个例子一样。

一种改进方案

    var button = document.getElementsByTagName("button");
    // 改良后
    var addEventListener = function(nodes){
        var helper = function(i){
            return function(e){
                alert(i);
            };
        };

        var i;

        for(i = 0; i < nodes.length; i++){
            nodes[i].onclick = helper(i);
        }
    }

    addEventListener(button);

大家可以到这里运行它.

另一种改进方案

    var button = document.getElementsByTagName("button");
    // 也可以这样

    var addEventListener = function(nodes){
        var i;

        for(i = 0; i < nodes.length; i++){
            nodes[i].onclick = (function(anotherI){
                return function(){
                    alert(anotherI);
                };
            })(i);
        }
    }

    addEventListener(button);​

大家可以到这里运行它.

但要避免在循环中创建函数,它可能只会带来无谓的计算,还会引起混淆。所以,最好还是采用第一种改进方案,虽然它比较费事。

参考:《javascript语言精粹》p36-39,《javascript设计模式》p29-30


blog comments powered by Disqus