来自未来的注意事项:这些历史性能差异不再准确或相关,因为现代引擎可以在没有可观察到的行为差异时let
使用var
语义来优化语义。当存在可观察到的差异时,使用正确的语义对性能几乎没有影响,因为相关代码本质上已经是异步的。
基于var
vs.的机制let
差异,运行时的这种差异是由于var
存在于匿名函数的整个块范围内,而let
仅存在于循环中,并且每次迭代都必须重新声明。* 见下文这是一个演示这一点的示例:
(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
现在更新和复杂得多。
细化
这里有一些对let
infor
循环的外行解释,对于那些不想查看公认的密集规范但又好奇如何let
在每次迭代中重新声明同时仍保持连续性的人。
但是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);
}
})();
Derek Ziemba提出了一个有趣的观点:
Internet Explorer 14.14393 似乎没有这些 [性能] 问题。
不幸的是,Internet Explorer通过使用更简单的语义错误地实现了let
语法var
,因此比较其性能是一个有争议的问题:
在Internet Explorer中,let
一个内for
环路初始化不会为每次循环迭代创建由ES2015限定一个单独的变量。相反,它的行为就像循环被包装在一个作用域块中,let
紧邻循环之前。
* Babel 的 REPL 上的这个转译版本演示了let
在for
循环中声明变量时会发生什么。创建一个新的声明性环境来保存该变量(详情请点击此处),然后对于每次循环迭代, 创建另一个声明性环境来保存每次迭代的变量副本;每次迭代的副本都是从前一个的值(详见此处)初始化的,但它们是单独的变量,正如每个闭包中输出的值所证明的那样。