TL;DR
理论上,该循环的未优化版本:
for (let i = 0; i < 500; ++i) {
doSomethingWith(i);
}
可能比相同循环的未优化版本慢var
:
for (var i = 0; i < 500; ++i) {
doSomethingWith(i);
}
因为一个不同的 i
为每个循环迭代创建变量let
,而只有一个i
with var
.
反对这一点事实是var
被提升,因此它在循环之外声明,而let
仅在循环内声明,这可能提供优化优势。
在实践中,在 2018 年,现代 JavaScript 引擎对循环进行了足够的内省,以了解何时可以优化该差异。 (甚至在那之前,你的循环很可能已经做了足够的工作,以至于额外的let
无论如何,相关的开销都被消除了。但现在你甚至不必担心它。)
谨防综合基准因为它们非常容易出错,并以真实代码不会的方式触发 JavaScript 引擎优化器(无论好坏方式)。但是,如果您想要一个综合基准,这里有一个:
const now = typeof performance === "object" && performance.now
? performance.now.bind(performance)
: Date.now.bind(Date);
const btn = document.getElementById("btn");
btn.addEventListener("click", function() {
btn.disabled = true;
runTest();
});
const maxTests = 100;
const loopLimit = 50000000;
const expectedX = 1249999975000000;
function runTest(index = 1, results = {usingVar: 0, usingLet: 0}) {
console.log(`Running Test #${index} of ${maxTests}`);
setTimeout(() => {
const varTime = usingVar();
const letTime = usingLet();
results.usingVar += varTime;
results.usingLet += letTime;
console.log(`Test ${index}: var = ${varTime}ms, let = ${letTime}ms`);
++index;
if (index <= maxTests) {
setTimeout(() => runTest(index, results), 0);
} else {
console.log(`Average time with var: ${(results.usingVar / maxTests).toFixed(2)}ms`);
console.log(`Average time with let: ${(results.usingLet / maxTests).toFixed(2)}ms`);
btn.disabled = false;
}
}, 0);
}
function usingVar() {
const start = now();
let x = 0;
for (var i = 0; i < loopLimit; i++) {
x += i;
}
if (x !== expectedX) {
throw new Error("Error in test");
}
return now() - start;
}
function usingLet() {
const start = now();
let x = 0;
for (let i = 0; i < loopLimit; i++) {
x += i;
}
if (x !== expectedX) {
throw new Error("Error in test");
}
return now() - start;
}
<input id="btn" type="button" value="Start">
它表示,V8/Chrome 或 SpiderMonkey/Firefox 上的综合测试没有显着差异。 (在两种浏览器中进行重复测试,其中一个获胜,或者另一个获胜,并且在两种情况下都在误差范围内。)但同样,这是一个综合基准,而不是您的代码。当您的代码存在性能问题时,请担心代码的性能。
就风格而言,我更喜欢let
如果我在闭包中使用循环变量,则可以获得范围优势和循环闭包优势。
Details
之间的重要区别var
and let
in a for
循环是一个不同的i
为每次迭代创建;它解决了经典的“循环闭包”问题:
function usingVar() {
for (var i = 0; i < 3; ++i) {
setTimeout(function() {
console.log("var's i: " + i);
}, 0);
}
}
function usingLet() {
for (let i = 0; i < 3; ++i) {
setTimeout(function() {
console.log("let's i: " + i);
}, 0);
}
}
usingVar();
setTimeout(usingLet, 20);
为每个循环体创建新的环境记录(规格链接)是工作,而工作需要时间,这就是为什么理论上let
版本比之前慢var
版本。
但只有当您在循环中创建一个使用以下函数(闭包)时,差异才重要i
,就像我在上面的可运行代码片段示例中所做的那样。否则,无法观察到这种区别,并且可以通过优化消除这种区别。
在 2018 年,V8(以及 Firefox 中的 SpiderMonkey)似乎正在进行充分的自省,在不使用let
的每次迭代变量语义。看这次测试.
在某些情况下,const
很可能提供一个优化的机会var
不会,特别是对于全局变量。
全局变量的问题在于它是全局的;any code anywhere可以访问它。所以如果你声明一个变量var
如果您从不打算更改(并且从不在代码中进行更改),则引擎不能假设它永远不会因为稍后加载的代码或类似的结果而发生更改。
With const
不过,您明确告诉引擎该值不能更改。因此,它可以自由地进行任何它想要的优化,包括发出文字而不是对使用它的代码的变量引用,因为知道这些值无法更改。
¹ 请记住,对于对象,其值是参考指向对象,而不是对象本身。所以与const o = {}
,您可以更改对象的状态(o.answer = 42
),但你不能o
指向一个新对象(因为这需要更改它包含的对象引用)。
使用时let
or const
其他var
-类似的情况,他们不太可能有不同的表现。无论您使用该函数,该函数都应该具有完全相同的性能var
or let
, 例如:
function foo() {
var i = 0;
while (Math.random() < 0.5) {
++i;
}
return i;
}
当然,这一切都不太重要,只有当确实有问题需要解决时才需要担心。