TL; DR
Theoretisch eine nicht optimierte Version dieser Schleife:
for (let i = 0; i < 500; ++i) {
doSomethingWith(i);
}
ist möglicherweise langsamer als eine nicht optimierte Version derselben Schleife mit var:
for (var i = 0; i < 500; ++i) {
doSomethingWith(i);
}
weil für jede Schleifeniteration mit eine andere i Variable erstellt wird let, während es nur eine imit gibt var.
Dagegen spricht die Tatsache, dass das varso hochgezogen wird, dass es außerhalb der Schleife deklariert wird, während das letnur innerhalb der Schleife deklariert wird, was einen Optimierungsvorteil bieten kann.
In der Praxis führen moderne JavaScript-Engines hier im Jahr 2018 eine ausreichende Selbstbeobachtung der Schleife durch, um zu wissen, wann sie diesen Unterschied beseitigen können. (Schon vorher hat Ihre Schleife wahrscheinlich genug Arbeit geleistet, dass der zusätzliche letOverhead ohnehin ausgewaschen wurde. Aber jetzt müssen Sie sich nicht einmal mehr darum kümmern.)
Passen Sie auf synthetische Benchmarks auf, da diese extrem leicht falsch sind, und lösen Sie JavaScript-Engine-Optimierer auf eine Weise aus, die echter Code nicht tut (sowohl auf gute als auch auf schlechte Weise). Wenn Sie jedoch einen synthetischen Benchmark wünschen, ist hier einer:
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">
Es heißt, dass es keinen signifikanten Unterschied in diesem synthetischen Test auf V8 / Chrome oder SpiderMonkey / Firefox gibt. (Wiederholte Tests in beiden Browsern haben den einen oder den anderen Gewinn und in beiden Fällen innerhalb einer Fehlergrenze.) Aber auch hier handelt es sich um einen synthetischen Benchmark, nicht um Ihren Code. Sorgen Sie sich um die Leistung Ihres Codes, wenn und wenn Ihr Code ein Leistungsproblem aufweist.
Aus letStilgründen bevorzuge ich den Scoping-Vorteil und den Closure-in-Loops-Vorteil, wenn ich die Loop-Variable in einem Closure verwende.
Einzelheiten
Der wichtige Unterschied zwischen varund letin einer forSchleife besteht darin, dass ifür jede Iteration ein anderer erstellt wird. Es befasst sich mit dem klassischen Problem "Closures in Loop":
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);
Das Erstellen des neuen EnvironmentRecord für jeden Schleifenkörper ( Spezifikationslink ) ist Arbeit, und Arbeit braucht Zeit, weshalb die letVersion theoretisch langsamer als die varVersion ist.
Der Unterschied ist jedoch nur wichtig, wenn Sie eine Funktion (Schließung) innerhalb der verwendeten Schleife erstellen i, wie ich es in diesem Beispiel für ein ausführbares Snippet oben getan habe. Andernfalls kann die Unterscheidung nicht beobachtet und wegoptimiert werden.
Hier im Jahr 2018 sieht es so aus, als würde V8 (und SpiderMonkey in Firefox) eine ausreichende Selbstbeobachtung durchführen, sodass in einer Schleife, die nicht die letSemantik der Variablen pro Iteration verwendet , keine Leistungskosten anfallen. Siehe diesen Test .
In einigen Fällen bietet sich constmöglicherweise eine Optimierungsmöglichkeit, vardie insbesondere bei globalen Variablen nicht möglich wäre.
Das Problem mit einer globalen Variablen ist, dass sie global ist. Jeder Code kann überall darauf zugreifen. Wenn Sie also eine Variable deklarieren, mit varder Sie niemals Änderungen vornehmen möchten (und die Sie niemals in Ihrem Code ändern), kann die Engine nicht davon ausgehen, dass sie sich aufgrund von später geladenem oder ähnlichem Code niemals ändern wird.
Mit constteilen Sie der Engine jedoch ausdrücklich mit, dass sich der Wert nicht ändern kann¹. Sie können also jede gewünschte Optimierung durchführen, einschließlich der Ausgabe eines Literals anstelle eines variablen Verweises auf Code, der es verwendet, da Sie wissen, dass die Werte nicht geändert werden können.
¹ Denken Sie daran, dass bei Objekten der Wert eine Referenz auf das Objekt ist, nicht auf das Objekt selbst. Mit const o = {}können Sie also den Status des Objekts ( o.answer = 42) ändern , aber Sie können nicht oauf ein neues Objekt verweisen (da dies das Ändern der darin enthaltenen Objektreferenz erfordern würde).
Bei Verwendung letoder constin varähnlichen Situationen ist es unwahrscheinlich, dass sie eine andere Leistung aufweisen. Diese Funktion sollte genau die gleiche Leistung haben, egal ob Sie varoder letzum Beispiel:
function foo() {
var i = 0;
while (Math.random() < 0.5) {
++i;
}
return i;
}
Es ist natürlich alles unwahrscheinlich und etwas, worüber man sich nur Sorgen machen muss, wenn ein echtes Problem zu lösen ist.
letVerwendung im Blockbereich leistungsfähiger sein als dievar, die keinen Blockbereich, sondern nur Funktionsbereich hat.