【问题标题】:Javascript variable copy scopeJavascript变量复制范围
【发布时间】:2013-12-28 07:15:08
【问题描述】:

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

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 绑定到该迭代中的函数。

但事实证明,j 绑定到所有迭代的最后一个 i 值。

那么,使用函数参数创建变量和var 有什么区别?

如有错误请指出!提前致谢!


谢谢大家!我不知道javascript只有函数和全局范围!责怪教我这样做的其他语言:P

【问题讨论】:

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

标签: javascript function scope closures


【解决方案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
}

【讨论】:

  • 所以var j 不会在每次迭代中创建一个新的 j ?
  • 不,for循环中没有特殊的作用域,所以j对于循环所在的任何作用域都是局部的,并且变量被提升,所以你需要一个闭包来保持值不变
【解决方案2】:

在您的第一个代码示例中,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 是在匿名立即函数中声明的,因此它在该迭代中被绑定到i 的值,因为传递值技巧。这称为关闭范围或简称为关闭

【讨论】:

    【解决方案3】:

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

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

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

    Javascript 实际上把它理解为

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

    这种变量提升在带有闭包的 for 循环中最令人困惑,但它也发生在其他类型的块中。例如,在具有常规词法范围的语言中,以下代码将打印“1”,因为内部“x”是一个单独的变量,仅存在于 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】:

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

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

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

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

      不,它没有。在您的第一个示例中只有 一个 j。在 JavaScript 中(目前),变量 only 具有函数或全局范围,从不阻塞范围。 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 函数。

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

      【讨论】:

      • 持久参考,这个词不错。你想出来了吗?
      • @FritsvanCampen:是的,我花了一段时间才想出这个短语,但当我最终做到时,我很高兴。
      【解决方案5】:

      这可行,使用 let 而不是 varlet 在 2013 年被问到问题时并不存在):

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

      【讨论】:

        猜你喜欢
        • 2010-12-07
        • 1970-01-01
        • 2014-10-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-07
        • 2012-12-23
        相关资源
        最近更新 更多