关于变量:循环内的javascript var声明

javascript var declaration within loop

1
2
3
4
5
6
7
8
9
10
11
12
/*Test scope problem*/
for(var i=1; i<3; i++){
    //declare variables
    var no = i;
    //verify no
    alert('setting '+no);

    //timeout to recheck
    setTimeout(function(){
        alert('test '+no);
    }, 500);
}

它按预期警告"设置1"和"设置2",但在超时后,它输出"测试2"两次-出于某种原因,变量"no"在第一个循环后不会重置…

我只找到了一个"丑陋"的解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*Test scope problem*/
var func=function(no){
    //verify no
    alert('setting '+no);

    //timeout to recheck
    setTimeout(function(){
        alert('test '+no);
    }, 500);
}
for(var i=1; i<3; i++){
    func(i);
}

关于如何更直接地解决这个问题有什么想法吗?还是只有这样?


Javascript没有块范围,变量声明被提升。这些事实加在一起意味着您的代码相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var no;

/*Test scope problem*/
for(var i=1; i<3; i++){
    //declare variables
    no = i;
    //verify no
    alert('setting '+no);

    //timeout to recheck
    setTimeout(function(){
        alert('test '+no);
    }, 500);
}

当超时函数执行时,循环将很长时间结束,并且no保留其最终值2。

解决这个问题的方法是将EDOCX1的当前值(1)传递到一个函数中,该函数为每个对EDOCX1的调用(9)创建一个新的回调。每次创建一个新函数意味着每个setTimeout回调都绑定到一个不同的执行上下文及其自己的变量集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var no;

/*Test scope problem*/
for(var i=1; i<3; i++){
    //declare variables
    no = i;
    //verify no
    alert('setting '+no);

    //timeout to recheck
    setTimeout( (function(num) {
            return function() {
                alert('test '+num);
            };
        })(no), 500);
}


这基本上与修复相同,但使用不同的语法来实现范围调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*Test scope problem*/
for (var i = 1; i < 3; i++) {
  //declare variables
  var no = i;
  //verify no
  alert('setting ' + no);

  //timeout to recheck
  (function() {
    var n = no;
    setTimeout(function() {
      alert('test ' + n);
    }, 500);
  })();
}


JavaScript没有词汇范围(for循环不创建新范围),您的解决方案是标准的解决方案。写这个的另一种方法可能是:

1
2
3
4
5
6
7
8
9
[1, 2].forEach(function(no){
    //verify no
    alert('setting '+no);

    //timeout to recheck
    setTimeout(function(){
        alert('test '+no);
    }, 500);
})

foreach()是在ecmascript 5中引入的,现在出现在现代浏览器中,但不是IE中。不过,您可以使用我的库来模拟它。


我喜欢我能从这个答案中得到很多里程数。

如果你需要帮助应用那个答案,请告诉我。

编辑

当然。让我们看看您的原始代码。

1
2
3
4
//timeout to recheck
setTimeout(function(){
    alert('test '+no);
}, 500);

看到那个匿名函数了吗?你进入埃多克斯的那个?直到计时器说是这样才调用它——在500毫秒时,在循环退出之后就可以调用了。

你所希望的是EDOCX1[1]被"就地"评估,但不是——它是在调用函数时评估的。到那时,no是2。

为了解决这个问题,我们需要一个在循环迭代期间执行的函数,它本身将返回一个setTimeout()可以按照我们期望的方式使用的函数。

1
2
3
4
5
6
7
8
9
setTimeout(function( value )
{
  // 'value' is closed by the function below
  return function()
  {
    alert('test ' + value );
  }
}( no ) // Here's the magic
, 500 );

因为我们创建了匿名函数并立即调用它,所以已经创建了一个新的范围,可以在其中关闭变量。这个范围是在value附近,而不是no附近。因为value为每个循环接收一个新的(ahem)值,所以每个lambda都有自己的值——我们想要的值。

因此,当setTimeout()激发时,它正在执行从闭包函数返回的函数。

我希望能解释清楚。