A note from the future: these historical performance differences are no longer accurate or relevant, as modern engines can optimize let
semantics by using var
semantics when there are no observable differences in behavior. When there are observable differences, using the correct semantics makes little difference in performance since the relevant code is already asynchronous in nature.
Based on the difference between the mechanics of var
vs. let
, this discrepancy in runtime is due to the fact that var
exists in the entire block scope of the anonymous function while let
exists only within the loop and must be re-declared for each iteration.* see below Here's an example demonstrating this point:
(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
在循环的所有迭代中共享 whilelet
不是。根据您的基准测试,node.js 似乎没有优化范围规则let
因为它比var
is.
阐述
这是一个简单的外行解释let
in for
循环,对于那些不想研究公认的密集规格,但好奇如何进行的人let
每次迭代都会重新声明,同时仍保持连续性。
But 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 所示:
"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);
}
})();
德里克·齐姆巴提出有趣的一点:
Internet Explorer 14.14393 似乎没有这些[性能]问题。
不幸的是,Internet Explorer执行不正确let本质上使用更简单的语法var语义,因此比较其性能是一个有争议的问题:
在 Internet Explorer 中,let
在一个for
循环初始值设定项不会按照 ES2015 的定义为每个循环迭代创建单独的变量。相反,它的行为就像循环被包裹在一个作用域块中一样let
紧接在循环之前。
* 这个转译版本Babel 的 REPL 演示了当你声明一个时会发生什么let
中的变量for
环形。创建一个新的声明性环境来保存该变量(详细信息在这里), 进而对于每个循环迭代 another创建声明性环境来保存变量的每次迭代副本;每次迭代的副本都是根据前一个迭代的值进行初始化的(详细信息在这里),但它们是单独的变量,正如每个闭包内输出的值所证明的那样。