【问题标题】:JavaScript variables declare outside or inside loop?JavaScript 变量是在循环外声明还是在循环内声明?
【发布时间】:2010-09-10 13:25:53
【问题描述】:

在 AS3 中,我认为您应该初始化循环外的所有变量以提高性能。 JavaScript也是这种情况吗?哪个更好/更快/最佳实践?

var value = 0;

for (var i = 0; i < 100; i++)
{
    value = somearray[i];
}

for (var i = 0 ; i < 100; i++)
{
    var value = somearray[i];
}

【问题讨论】:

  • 在外面!总是在外面。
  • 嗯,在 Javascript 和 AS3 中变量声明不会被推到函数范围吗?如果我是对的,那真的没关系。
  • @Andy - 在函数体中声明之前,您是否尝试过分配?也许你的先入之见让你误入歧途。 WRT 性能,具有俯卧撑作用域,如果 JS 被解释,那么它将在循环块内咀嚼额外的循环。如果编译(现在大多数引擎都这样做)就没有关系了。
  • 好问题!谢谢。阅读所有答案后,我相信如果它只是一个小循环或只是一个临时变量,我会将它们保留在需要的地方并且不会影响性能。如果一个 var 在函数内多次使用,为什么不在函数内引用它,最后全局变量可以放在 fn() 之外
  • 我很惊讶没有人试图衡量性能。我创建了一个jsperf。在 Safari 和 Firefox 的循环中声明时似乎要快一些,而 Chrome 则相反……

标签: javascript performance


【解决方案1】:

在 JavaScript 或 ActionScript 的含义或性能上,绝对没有区别

var 是解析器的指令,不是在运行时执行的命令。如果在函数体 (*) 中的任何位置已将特定标识符声明为 var 一次或多次,则块中对该标识符的所有使用都将引用局部变量。 value 在循环内、循环外或两者都声明为 var 没有区别。

因此,您应该编写您认为最易读的任何一个。我不同意 Crockford 的观点,即把所有的变量放在函数的顶部总是最好的。对于在一段代码中临时使用变量的情况,最好在该段中声明var,以便该段独立,可以复制粘贴。否则,在重构期间将几行代码复制粘贴到一个新函数中,而无需单独挑选和移动相关联的var,您就会意外得到一个全局变量。

特别是:

for (var i; i<100; i++)
    do something;

for (var i; i<100; i++)
    do something else;

Crockford 会建议您删除第二个 var(或同时删除 vars 并在上面执行 var i;),jslint 会为此抱怨您。但是 IMO 保留 vars 更易于维护,将所有相关代码放在一起,而不是在函数顶部有额外的、容易忘记的代码。

就我个人而言,我倾向于在独立的代码段中将变量的第一个赋值声明为var,无论在同一函数的其他部分中是否存在对同一变量名的另一种单独使用。对我来说,必须声明 var 是一个不受欢迎的 JS 疣(最好让变量默认为本地);我不认为我有责任在 JavaScript 中复制 [an old revision of ANSI C] 的限制。

(*: 嵌套函数体除外)

【讨论】:

  • 我仍然无法决定我是否与 Crockford 合作。在块内声明变量感觉有点像使用条件函数语句(这很顽皮)......但是,我也同意你的观点:) #confused
  • +1 我不同意 Crockford 的推理(引用 Daniel 的回答),作为 JavaScript 开发人员,我们不应该编写代码以便其他“C 系列”程序员能够理解它。我经常在 if 块和循环中使用 var,因为它对我来说更有意义。
  • -1 OP 询问是否应在循环开始之前声明循环体中的变量。循环的索引值显然是一种特殊情况(并且被提升)并且根本没有帮助 OP。
  • 循环索引不是特殊情况,它们的处理和提升方式与正常分配完全相同。
  • +1 克罗克福德对这个(和其他人,但我离题了)是错误的。要求 var 仅在函数顶部使用只是要求意外创建全局变量。在一个地方声明大量不相关的变量在语义上毫无意义,尤其是当其中一些变量最终可能永远不会被使用时。
【解决方案2】:

理论上它不应该对 JavaScript 产生任何影响,因为该语言没有块作用域,而只有函数作用域。

我不确定性能参数,但Douglas Crockford 仍然建议var 语句应该是函数体中的第一个语句。引用Code Conventions for the JavaScript Programming Language:

JavaScript 没有块作用域,因此在块中定义变量会使熟悉其他 C 系列语言的程序员感到困惑。在函数顶部定义所有变量。

我认为他有一个观点,正如您在以下示例中看到的那样。在函数顶部声明变量不应使读者误以为变量i 是在for 循环块的范围内:

function myFunction() {
  var i;    // the scope of the variables is very clear

  for (i = 0; i < 10; i++) {
    // ...
  }
}

【讨论】:

  • +1 用于讲述关于 JS 范围的 OP 真相。我想知道是否要否决其他答案!
  • @Kieranmaine:我没有说它不会影响性能。我只是提出了将它们放在循环之外的论点,与性能无关......我没有任何关于性能论点的参考资料,但你也没有在你的答案中引用任何内容:)
  • @Kieranmaine:你有消息来源吗?
  • @Kieranmaine: AFAIK 即使您在循环内声明变量,ecma- / javascript 也会在运行时增加这些变量。这就是所谓的“吊装”。所以应该没有什么区别。
  • ES6 的let 如何影响这个答案?
【解决方案3】:

ECMA-/Javascript 语言 hoists 在函数顶部的任何位置声明的任何变量。这是因为这种语言确实function scope,而没有block scope,就像许多其他类似C的语言一样。
这也称为lexical scope

如果你声明类似

var foo = function(){
    for(var i = 0; i < 10; i++){
    }
};

这会将hoisted 发送到:

var foo = function(){
    var i;
    for(i = 0; i < 10; i++){
    }
}

所以它对性能没有任何影响(但如果我在这里完全错了,请纠正我)。
在函数顶部以外的其他地方声明变量的一个更好的论据是可读性。在 for-loop 中声明变量可能会导致错误的假设,即该变量只能在循环体中访问,这是完全错误。事实上,您可以在当前范围内的任何位置访问该变量。

【讨论】:

  • 与已接受的基本答案相同,但 IMO 更具可读性且信息量差不多。干得好。
  • ES6 的let 如何影响这个答案?
【解决方案4】:

明年,所有浏览器都将拥有预编译代码的 JS 引擎,因此性能差异(来自一次又一次地解析相同的代码块加上执行分配)应该可以忽略不计。

此外,除非必要,否则永远不要优化性能。将变量保持在您第一次需要它们的位置附近可以使您的代码保持干净。不利的一面是,习惯使用块范围语言的人可能会感到困惑。

【讨论】:

    【解决方案5】:

    另一个考虑因素是,现在我们在 ES2015 中有 letconst,您现在可以将变量范围专门用于循环块。因此,除非您需要在循环外使用相同的变量(或者如果每次迭代都依赖于在前一次迭代中对该变量执行的操作),否则最好这样做:

    for (let i = 0; i < 100; i++) {
        let value = somearray[i];
        //do something with `value`
    }
    

    【讨论】:

    • 我们不是在每次迭代中都重新声明价值吗? let 怎么允许这样做?
    • {} 之间的部分是它自己的范围; let value 特定于该块(我想也是该特定迭代)并且在下一次迭代中不再存在
    【解决方案6】:

    我刚刚在 Chrome 中做了一个简单的测试。在浏览器中尝试fiddle 并查看结果

      var count = 100000000;
        var a = 0;
        console.log(new Date());
    
        for (var i=0; i<count; i++) {
          a = a + 1
        }
    
        console.log(new Date());
    
        var j;
        for (j=0; j<count; j++) {
          a = a + 1;
        }
    
        console.log(new Date());
    
        var j;
        for (j=0; j<count; j++) {
            var x;
            x = x + 1;
        }
    
        console.log(new Date());
    

    结果是最后一次测试需要约 8 秒,而前 2 次只需约 2 秒。非常可重复,无论顺序如何。

    所以,这向我证明,应该始终在循环之外声明变量。对我来说奇怪的情况是我在 for() 语句中声明 i 的第一个案例。这个似乎和我预先声明索引的第二次测试一样快。

    【讨论】:

    • @KP:结果只有在您自己测试或大量人员验证时才能得到证明。 @mkoistinen:我构建了一个更公平的测试,jsfiddle.net/GM8nk。在 Chrome 5 中多次运行该脚本后,我可以看到没有一致的赢家。经过几次刷新后,所有三种变体的表现都比其他变体更好。 -1 来自我,我害怕。请注意,您可能希望在其他浏览器中运行 this one。 IE 和 Fx 不喜欢 1 亿次迭代。
    • @AndyE。哇,所以基于这个简单的测试,IE 吸了 100 倍以上? =)
    • 结果对我来说到处都是,虽然有时存在显着的速度差异,但没有明显的跨浏览器赢家。奇怪的。我认为安迪的小提琴是一个更好的测试,将每个候选人放在自己的函数中......当然,如果原始脚本在函数之外运行,它不应该真正测试任何东西,因为var 声明为全局无论如何都是全局变量。
    • 事后一年多,但SHAPOW
    • 这不是我的,但我想你们中的一些人会感兴趣:jsperf.com/var-in-for-loop
    【解决方案7】:

    JavaScript 是底层由 C 或 C++ 编写的一种语言,我不太确定它是哪一种。其目的之一是节省处理内部存储器的乐趣。 即使在 C 或 C++ 中,您也不必担心在循环内声明变量时是否会消耗大量资源。为什么要在 JavaScript 中担心它?

    【讨论】:

    • C 或 C++ 可能位于 JavaScript 的底部。但我们不应该忘记,浏览器将 JavaScript 转换为底层语言(C、C++)。所以性能取决于浏览器
    • @Kira 它实际上并没有转换为 C/C++。 Javascript 被编译成一组指令,这些指令由 JS 运行时(即在浏览器中运行的虚拟机)执行。同样的原则也适用于其他动态语言,例如 Python 和 Ruby。
    • @AnthonyE,感谢您的信息。我不确定 JS 是否会转换为 C 或 C++。所以我在评论中使用了maybe
    【解决方案8】:

    我更喜欢将可读性和性能结合起来。 所以我最喜欢的是在循环中声明变量,这意味着我会有块范围封装。

    for (let i = 0, sum = 0; i < count; i++) { // count also be declared here like count = array.length;
      sum = sum + 1;
    }
    

    根据之前提供的小提琴演奏tests,获胜者是 4 号

    【讨论】:

      【解决方案9】:

      嗯,这取决于您要实现的目标...如果 value 假设只是循环块内的一个临时变量,那么使用第二种形式会更清楚。它也更加合乎逻辑和冗长。

      【讨论】:

      • 我发现将所有变量声明推到顶部(包括临时变量)实际上会导致混淆,因为它只会变得“嘈杂”。
      【解决方案10】:

      在 for 循环内部或外部声明变量都没有区别。 下面是要测试的示例代码。

      function a() {
         console.log('Function a() starts');
         console.log(new Date());
          var j;
          for (j=0; j<100000000; j++) {
              var x;
              x = x + 1;
          }
          console.log(new Date());
          console.log('Function a() Ends');
      }
      a()
      function b() {
      console.log('Function B() starts');
         console.log(new Date());
          var a;
          var j;
          for (j=0; j<100000000; j++) {
            a = a + 1;
          }
          console.log(new Date());
          console.log('Function B() Ends');
      }
      b()
      

      结果显示在我的案例中

      Function a() starts
      VM121:3 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
      VM121:9 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
      VM121:10 Function a() Ends
      VM121:14 Function B() starts
      VM121:15 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
      VM121:21 Thu Apr 12 2018 15:20:26 GMT+0530 (India Standard Time)
      VM121:22 Function B() Ends
      

      谢谢—— MyFavs.in

      【讨论】:

      • 在这两种情况下,您都在循环之外声明 j! X_x
      • 我在 Chromium 81 中尝试使用 let 而不是 vara() 往往会慢一些(比如 120 vs 115 ms = ~6 % = IMO 微不足道)
      【解决方案11】:

      这里的问题基本上是在循环中声明一个 var。想想如果你这样做会发生什么:

      var a = 30;
      var a = 50;
      var a = 60;
      

      你认为这是对的吗?不...因为您不想多次声明变量。当你在循环中声明一个变量时,它不是声明循环运行的次数吗?显然,当您处于“使用严格”模式时,它会打你一巴掌。人们不考虑最初的问题就与 Crockford 产生了分歧。

      所以在上面声明变量总是好的——1.为了可读性,2.养成好习惯。

      【讨论】:

      • “当你在循环中声明一个变量时,它不是声明循环运行的次数吗?”
      【解决方案12】:

      关于在 Linux 操作系统上的 Chrome、Firefox 和 jsperf 上运行测试后的性能,循环中和循环外的变量声明之间似乎存在性能差异。这是一个很小的差异,但这也因迭代次数和变量声明的数量而变得复杂。

      因此,为了获得最佳性能,我不得不建议在循环外声明变量。或者更好的是在行中声明您的变量。见例子。

      // inline
      for (var ai = 0, al = 100000000, av; ai < al; ai++) {
          av = av + 1;
      }
      
      // outside
      var bv;
      var bl = 100000000;
      for (var bi = 0; bi < bl; bi++) {
          bv = bv + 1;
      }
      

      请注意变量 'al' 和 'av' 在 for 循环声明行中的位置。这个内联声明为我提供了始终如一的更好性能。甚至在循环外声明变量。同样,性能差异非常小。

      https://jsperf.com/outside-inline-for-loop-ase/1

      【讨论】:

      • 对我来说,您的测试在循环中给出。然而它没有,差异太小而无法得出结论,并且接受的答案清楚地解释了没有区别
      • 随着变量声明的提升,确实没有区别。
      猜你喜欢
      • 2011-04-10
      • 2015-04-12
      • 2014-03-24
      • 1970-01-01
      • 2014-06-15
      • 1970-01-01
      • 2022-11-04
      • 2012-02-02
      • 1970-01-01
      相关资源
      最近更新 更多