为什么在nodejs的for循环中让var慢于var?

时间:2016-06-13 14:53:35

标签: javascript node.js ecmascript-6 v8

我写了一个非常简单的基准:

console.time('var');
for (var i = 0; i < 100000000; i++) {}
console.timeEnd('var')


console.time('let');
for (let i = 0; i < 100000000; i++) {}
console.timeEnd('let')

如果你正在运行Chrome,可以在这里试试(因为NodeJS和Chrome使用相同的JavaScript引擎,虽然版本通常略有不同):

&#13;
&#13;
// Since Node runs code in a function wrapper with a different
// `this` than global code, do that:
(function() {
  console.time('var');
  for (var i = 0; i < 100000000; i++) {}
  console.timeEnd('var')


  console.time('let');
  for (let i = 0; i < 100000000; i++) {}
  console.timeEnd('let')
}).call({});
&#13;
&#13;
&#13;

结果令我惊讶:

var: 89.162ms
let: 320.473ms

我在Node 4.0.0&amp;&amp; 5.0.0&amp;&amp; 6.0.0以及varlet之间的比例对于每个节点版本都是相同的。

有人可以向我解释一下这个看似奇怪的行为的原因是什么?

2 个答案:

答案 0 :(得分:15)

根据varlet的机制之间的差异,它与匿名函数的整个块范围中存在var的事实有关let仅存在于循环中,必须为每次迭代重新声明。 1 以下是一个证明这一点的例子:

&#13;
&#13;
(function() {
  for (var i = 0; i < 5; i++) {
    setTimeout(function() {
      console.log(`i: ${i} seconds`);
    }, i * 1000);
  }
  // 5, 5, 5, 5, 5


  for (let j = 0; j < 5; j++) {
    setTimeout(function() {
      console.log(`j: ${j} seconds`);
    }, 5000 + j * 1000);
  }
  // 0, 1, 2, 3, 4
}());
&#13;
&#13;
&#13;

请注意,i在循环的所有迭代中共享,而let则不是。根据您的基准测试,似乎node.js还没有优化let的范围规则,因为它比var更加新近和复杂。

精化

这里有let for letlet循环中的小外行解释,对于那些不关心密切关注密集规范的人,但很好奇{{1}为每次迭代重新声明,同时仍然保持连续性。

  

但是(function() { for (let j = 0; j < 5; j++) { j++; // see how it skips 0, 2, and 4!?!? setTimeout(function() { console.log(`j: ${j} seconds`); }, j * 1000); } }());可能无法为每次迭代重新声明,因为如果你在循环中更改它,它会传播到下一次迭代!

首先,这是一个几乎可以验证这种潜在反驳论点的例子:

&#13;
&#13;
j
&#13;
&#13;
&#13;

你是部分正确的,因为这些变化尊重"use strict"; (function () { var _loop = function _loop(_j) { _j++; // here's the change inside the new scope setTimeout(function () { console.log("j: " + _j + " seconds"); }, _j * 1000); j = _j; // here's the change being propagated back to maintain continuity }; for (var j = 0; j < 5; j++) { _loop(j); } })();的连续性。但是,正如Babel所证明的那样,它仍然会在每次迭代时重新声明:

&#13;
&#13;
let
&#13;
&#13;
&#13;

就像有人说的那样。复杂的规则。毫无疑问,基准测试表现出如此大的性能差异(目前)。希望将来能够进一步优化。

1:请参阅Babel的REPL上的this transpiled version以了解此情况。在for循环中声明{{1}}变量时会发生什么情况,即创建一个新的声明性环境来保存该变量(details here),然后为每个循环设置迭代 创建另一个声明性环境以保存变量的每次迭代副本;每个迭代的副本都是从前一个值(details here)初始化的,但是它们是分开的变量,如链接所示,由闭包输出的值。

答案 1 :(得分:2)

对于这个问题。我试着找一些线索形式的chrome V8源代码。这是V8环路剥离代码:

https://github.com/v8/v8/blob/5.4.156/src/compiler/loop-peeling.cc

我试着理解它,我认为for循环在实现中有一个中间层。 for循环将保持中间层的增量值。

如果循环使用let声明“i”,V8将为每个循环迭代声明一个新变量i,将中间层增量变量的值复制到新声明的“i”,然后将其放入循环体范围;

如果循环使用var来声明“i”,则V8只会将中间层增量值引用放入循环体范围。它将降低循环迭代的性能开销。

抱歉我的游泳池英语。 v8源代码中有一个图表,它会显示机制。