看下面这段简短的例子,猜猜它运行之后会输出什么结果:

function foo()
{
    var i;
    for (i = 0; i < 5; ++i)
    {
        setTimeout((function() {
            alert(i);
            }), 0);
    }
}

foo();

刚学 JavaScript 的时候,我一直以为这段代码会弹出五个对话框,分别显示“0, 1, 2, 3, 4”。直到有一天在实际项目中吃亏了之后,才发现,闭包这个概念不是我想象的样子……

这里的 setTimeout 里给的参数是一个函数,这个函数访问了函数 foo 里的局部变量 i,注意:是局部变量 i 本身,不是 i 的值。这个函数是在 foo 退出后,才被 setTimeout 间接调用的,于是 i 的析构时机被延迟了。我们创建了 5 个函数,并传递给了 5 个 setTimeout,但这 5 个函数中访问的变量 i 实际上都是同一个,即 foo 中的那一个局部变量。由于循环结束后 i 的值是 5,因此这段代码会弹出 5 个对话框,分别显示“5, 5, 5, 5, 5”。

如果想要让弹出的对话框显示循环当前位置的值的话,我们可以用这样的代码实现:

function foo()
{
    var i;
    for (i = 0; i < 5; ++i)
    {
        setTimeout((function(num) {
                return function()
                {
                    alert(num);
                }
            })(i), 0);
    }
}

foo();

我们创建了一个函数,让这个函数返回一个函数,再在返回的函数中弹出我们所要的对话框。由于这里出现了一次调用传值的过程,因此在返回的函数中访问的 i,就不再是 foo 里的局部变量 i 了。

我当时在做一个项目的时候,遇到的问题比这里的情况稍复杂,IE 处理不了那种情况,错误地按照传值的方式处理了我传入的局部变量,于是我得到了我预期的,但却是错误的结果。后来在 Firefox 下修正这个错误时,耗去了我整整半个晚上……