TL; DR
En théorie , une version non optimisée de cette boucle:
for (let i = 0; i < 500; ++i) {
doSomethingWith(i);
}
peut être plus lent qu'une version non optimisée de la même boucle avec var
:
for (var i = 0; i < 500; ++i) {
doSomethingWith(i);
}
car une variable différente i
est créée pour chaque itération de boucle avec let
, alors qu'il n'y en a qu'une seule i
avec var
.
Contre cela, c'est le fait que le var
est hissé, donc il est déclaré en dehors de la boucle alors que le let
n'est déclaré que dans la boucle, ce qui peut offrir un avantage d'optimisation.
En pratique , ici en 2018, les moteurs JavaScript modernes font suffisamment d'introspection de la boucle pour savoir quand ils peuvent optimiser cette différence. (Même avant, il y a de fortes chances que votre boucle fasse suffisamment de travail pour que les let
frais généraux supplémentaires soient supprimés de toute façon. Mais maintenant, vous n'avez même plus à vous en soucier.)
Méfiez-vous des benchmarks synthétiques car ils sont extrêmement faciles à se tromper et déclenchent les optimiseurs de moteur JavaScript d'une manière que le vrai code ne fait pas (à la fois bonne et mauvaise). Cependant, si vous voulez un benchmark synthétique, en voici un:
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">
Il dit qu'il n'y a pas de différence significative dans ce test synthétique sur V8 / Chrome ou SpiderMonkey / Firefox. (Les tests répétés dans les deux navigateurs ont l'un gagnant, ou l'autre gagnant, et dans les deux cas dans une marge d'erreur.) Mais encore une fois, c'est une référence synthétique, pas votre code. Souciez-vous des performances de votre code quand et si votre code a un problème de performances.
En ce qui concerne le style, je préfère let
pour l'avantage de la portée et l'avantage de la fermeture dans les boucles si j'utilise la variable de boucle dans une fermeture.
Détails
La différence importante entre var
et let
dans une for
boucle est qu'un différent i
est créé pour chaque itération; il résout le problème classique des "fermetures en boucle":
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);
Créer le nouvel EnvironmentRecord pour chaque corps de boucle ( lien de spécification ) est un travail, et le travail prend du temps, c'est pourquoi en théorie la let
version est plus lente que la var
version.
Mais la différence n'a d'importance que si vous créez une fonction (fermeture) dans la boucle qui utilise i
, comme je l'ai fait dans cet exemple d'extrait de code exécutable ci-dessus. Sinon, la distinction ne peut pas être observée et peut être optimisée.
Ici en 2018, il semble que V8 (et SpiderMonkey dans Firefox) fasse une introspection suffisante pour qu'il n'y ait aucun coût de performance dans une boucle qui n'utilise pas la let
sémantique de variable par itération de. Voir ce test .
Dans certains cas, cela const
pourrait bien offrir une opportunité d'optimisation qui var
ne le serait pas, en particulier pour les variables globales.
Le problème avec une variable globale est qu'elle est, eh bien, globale; n'importe quel code n'importe où pourrait y accéder. Donc, si vous déclarez une variable avec var
laquelle vous n'avez jamais l'intention de changer (et ne changez jamais votre code), le moteur ne peut pas supposer qu'elle ne changera jamais à la suite d'un code chargé plus tard ou similaire.
Avec const
, cependant, vous dites explicitement au moteur que la valeur ne peut pas changer¹. Il est donc libre de faire toute optimisation qu'il souhaite, y compris d'émettre un littéral au lieu d'une référence de variable au code qui l'utilise, sachant que les valeurs ne peuvent pas être modifiées.
¹ N'oubliez pas qu'avec les objets, la valeur est une référence à l'objet, pas à l'objet lui-même. Donc avec const o = {}
, vous pouvez changer l'état de l'objet ( o.answer = 42
), mais vous ne pouvez pas o
pointer vers un nouvel objet (car cela nécessiterait de changer la référence d'objet qu'il contient).
Lors de l'utilisation let
ou const
dans d'autres var
situations similaires, il est peu probable qu'ils aient des performances différentes. Cette fonction doit avoir exactement les mêmes performances que vous utilisiez var
ou let
, par exemple:
function foo() {
var i = 0;
while (Math.random() < 0.5) {
++i;
}
return i;
}
Tout cela, bien sûr, n'a pas d'importance et quelque chose à craindre uniquement si et quand il y a un vrai problème à résoudre.
let
utilisé dans la portée de bloc devrait être plus performant quevar
, qui n'a pas de portée de bloc, mais seulement une portée de fonction.