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 iavec var.
Contre cela, c'est le fait que le varest hissé, donc il est déclaré en dehors de la boucle alors que le letn'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 letfrais 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 letpour 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 varet letdans une forboucle est qu'un différent iest 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 letversion est plus lente que la varversion.
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 letsémantique de variable par itération de. Voir ce test .
Dans certains cas, cela constpourrait bien offrir une opportunité d'optimisation qui varne 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 varlaquelle 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 opointer vers un nouvel objet (car cela nécessiterait de changer la référence d'objet qu'il contient).
Lors de l'utilisation letou constdans d'autres varsituations similaires, il est peu probable qu'ils aient des performances différentes. Cette fonction doit avoir exactement les mêmes performances que vous utilisiez varou 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.
letutilisé 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.