Javascript变量复制范围

时间:2013-12-10 16:39:41

标签: javascript function scope closures

我有两个代码块,我认为应该产生相同的结果:

1

for(var i=0;i<10;i+=1){
    var j=i;
    setTimeout(function(){
        console.log(j);
    },100);
}

2

for(var i=0;i<10;i+=1){
    (function(j){
        setTimeout(function(){
            console.log(j);
        },100);
    })(i);
}

然而,正如大多数人所预料的那样,第一个记录9次,第二次记录正确,从0到9。

第二个是使用闭包来保留i的值。我认为第一个也应该保留价值,因为:

  1. var j在每次迭代中创建一个新变量j。
  2. 当前i值已在迭代中分配给此新j
  3. 这个新的j然后在同一次迭代中绑定到setTimeout的函数。
  4. 下一次迭代会将新j绑定到该迭代中的函数。
  5. 但事实证明,j绑定到所有迭代的最后i值。

    那么,使用函数参数和var创建变量之间的区别是什么?

    请指出任何错误!提前谢谢!


    谢谢大家!我不知道javascript只有功能和全局范围!归咎于教会我这样做的其他语言:P

5 个答案:

答案 0 :(得分:5)

  

第二个是使用闭包来保留i的值。

事实上,你所看到的两个结果都是闭包工作的结果。您传递给setTimeout的两个函数都是闭包。

  

我认为第一个也应保留价值,因为:

     

var j在每次迭代中创建一个新的变量j。

不,它没有。您的第一个示例中只有一个 j。在JavaScript(目前)中,变量具有函数或全局范围,从不阻止范围。 JavaScript实际上对你的第一个例子做了什么看起来更像是这样:

var i;
var j;

for(i=0;i<10;i+=1){
    j=i;
    setTimeout(function(){
        console.log(j);
    },100);
}

结果闭包有一个持久引用到一个j变量,这就是为什么你一遍又一遍地得到相同的值。

你的第二个例子是一个反模式,因为它既难以阅读,又在每个循环中不必要地创建和丢弃函数。让我们通过添加一些变量来使其变得更加明显;这段代码与第二个例子完全相同,唯一的变化是我添加的中间变量:

for(var i=0;i<10;i+=1){
    var f1 = function(j){
        var f2 = function(){
            console.log(j);
        };
        setTimeout(f2,100);
    };
    f1(i);
}

相反:

for(var i=0;i<10;i+=1){
    setTimeout(makeHandler(i));
}

function makeHandler(j){
    return function(){
        console.log(j);
    };
}

更容易阅读,避免在每个循环上重新创建makeHandler函数。

更多探索(在我的博客上)

答案 1 :(得分:1)

它是setTimeout,它推迟执行直到浏览器可用,或者在这种情况下0.1秒后,并且当循环阻塞浏览器时,timeOuts直到循环完成后才会执行,到那时变量i等于它在循环中设置的最后一个东西,因为循环在setTimeout中的代码执行之前已经完成。

通过将变量作为参数传递给另一个函数作为循环内部的参数,该变量对于该函数作用域是本地的,并保持其值与迭代无关。

for(var i=0;i<10;i+=1){
    var j=i;                // j is constantly updated and it's also hoisted
    setTimeout(function(){  // and this runs last, when the browser is no longer
        console.log(j);     // locked, so j is the last value it was set to
    },100);
}

for(var i=0;i<10;i+=1){
    (function(j){ // creates a new scope with a local variable, the argument j
        setTimeout(function(){
            console.log(j);
        },100);
    })(i); // passing i to the functions scope
}

答案 2 :(得分:1)

在您的第一个代码示例中,j未绑定循环的迭代中i 的值。这是因为您无法在循环中声明变量。循环在JavaScript中没有范围,只有函数具有范围。

第一个代码示例相当于:

var i, j; // all variable declarations are hoisted to the top
for (i = 0; i < 10; i += 1) {
    j = i;
    setTimeout(function() {
        console.log(j);
    }, 100);
}

因为在第二个代码中,样本j在anonymous-immediate函数中声明,所以在该迭代中它被绑定到i的值,因为值为的传递 / em>的。这称为关闭范围或只是闭包

答案 3 :(得分:0)

  

var j在每次迭代中创建一个新的变量j。

您认为不幸的是Javascript的想法。在Javascript中,所有变量声明都移动到封闭函数的顶部,因此当您键入

function(){
    for(var i=1; i<10; i++){

Javascript实际上将其理解为

function(){
    var i;
    for(i=1; i<20; i++){

这个变量提升在带闭包的for循环中最容易混淆,但它也发生在其他类型的块中。例如,在具有常规词汇范围的语言中,以下代码将打印&#34; 1&#34;因为内心&#34; x&#34;是一个单独的变量,只存在于if语句的那个​​分支中:

var x = 1;
if(true){
   var x = 2;
}
console.log(x);

然而,在Javascript中,内部变量声明被提升出来,所以它实际上好像你已经写了

var x;
x = 1;
if(true){
   x = 2;
}
console.log(x);

答案 4 :(得分:0)

这是有效的,使用let代替varlet在2013年提出问题的时候并没有回复:

for(let i=0;i<10;i+=1){
    let j=i;
    setTimeout(function(){
        console.log(j);
    },100);
}