我写了一个非常简单的基准:
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引擎,尽管通常版本略有不同):
// 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({});
结果令我惊讶:
var: 89.162ms let: 320.473ms
我已经在Node 4.0.0 && 5.0.0 && 6.0.0中对其进行了测试,每个节点版本之间的var和比例let相同。
var
let
有人可以向我解释这种看似奇怪的行为的原因是什么?
基于varvs. 的机制差异let,这与以下事实有关:var存在于匿名函数的整个块范围中,而let仅存在于循环中,并且每次迭代都必须重新声明。1这是说明这一点的示例:
(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 }());
请注意,在i循环的所有迭代中共享,而let不是。根据您的基准,似乎node.js尚未针对其优化作用域规则,let因为它比以前更新且复杂得多var。
i
这是对letin for循环的一些外行解释,适用于那些不关心细致的规范,但好奇如何let在每次迭代中重新声明而又保持连续性的人。
for
但是let不可能每次迭代都重新声明,因为如果在循环内更改它,它将传播到下一个迭代!
首先,这里有一个几乎可以证明这一潜在反论点的例子:
(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); } }());
您部分正确,因为更改尊重的连续性j。但是,对于每次迭代,仍然需要重新声明它,如Babel所示:
j
"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); } })();
就像有人说的那样。复杂的规则。毫无疑问,基准测试表现出如此巨大的性能差异(目前)。希望将来会进一步优化。
1:在Babel的REPL上查看此转译版本,以观看演示。let在这样的for循环中声明变量时会发生什么情况,即创建了一个新的声明性环境来保存该变量(在此处进行详细说明),然后 为每次循环迭代 创建 另一个 声明性环境来保存该变量的每次迭代副本; 每次迭代的副本都是从前一个的值初始化的(此处有详细信息),但是它们是独立的变量,如闭包所输出的值所示。