作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰地说明这个问题。

function createFunction(){
    var result = new Array();

    for (var i=0;i<10;i++){
        result[i] = function(){
            return i;
        }
    }
    return result;
}

  这个函数会返回一个函数数组。从表面上看,似乎每个函数都应该返回自己的索引值。即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上,每个函数都会返回10,因为每个函数的作用域链中都保存着createFunction()函数的活动对象,所以他们应用的都是同一个变量i。当createFunction()函数返回后,变量i的值是10,此时每个函数都引用这保存变量i的同一个变量对象。所以在每一个函数内部i的值都是10.但是,我们可以创建另一个匿名函数强制让闭包的行为符合预期,如下所示:

代码一(A):(出自javascript高级程序设计第三版181页闭包)

function creatFunction(){
    var result = new Array();

    for(var i=0;i<10;i++){
        result[i] = function(num){
            return function(){
                console.log(num);
                return num;
            }
        }(i)
    }
    return result;
}
creatFunction();

  现在代码A可以让每个函数返回自己的索引值了,在这个版本中,我们没有把闭包直接赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i由于函数参数都是按值传递的,所以就会将变量i的当前值复制给参数num,而在这个匿名函数内部,又创建并返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了。

  现在我们来看看代码A和代码B的区别:
代码二(B):

function creatFunction(){
    var result = new Array();

    for(var i=0;i<10;i++){
        result[i] = function(){
            return function(num){
                console.log(num);
                return num;
            }(i)
        }
    }
    return result;
}
creatFunction();

  A代码和B代码的大致意思是在creatFunction函数内部把function对象数组赋值给result并返回,但A代码的
result[i] = function(num){...}(i)的意思是将外部作用域的变量i赋值给num形参,相当于调用这个匿名函数。

  我们先看几个简单的例子:

alert((function(x,y){return x+y;})(2,3));// "5" 
alert((new Function("x","y","return x*y;"))(2,3));// "6" 
alert((function(x,y){return x+y;})(2,3));// "5"
alert((new Function("x","y","return x*y;"))(2,3));// "6"

  很多人或许会奇怪,为什么这种方法能成功调用呢?觉得这个应用奇怪的人就看一下我以下这段解释吧。

  大家知道小括号的作用吗?小括号能把我们的表达式组合分块,并且每一块,也就是每一对小括号,都有一个返回值。这个返回值实际上也就是小括号中表达式的返回值。所以,当我们用一对小括号把匿名函数括起来的时候,实际上小括号对返回的,就是一个匿名函数的Function对象。因此,小括号对加上匿名函数就如同有名字的函数般被我们取得它的引用位置了。所以如果在这个引用变量后面再加上参数列表,就会实现普通函数的调用形式。

  所以代码B相当于,注意这里的相当是指的最终的结果相当,由于最内层的return没有加(i),返回值会是个函数对象,如果像代码B一样加上(i),就会执行这个返回的函数对象内部的语句,即输出并返回一个值:

function creatFunction(){
    var result = new Array();

    for(var i=0;i<10;i++){
        result[i] = function(){
            return function(){
                console.log(num);
                return num;
            }
        }
    }
    return result;
}
creatFunction();