Ce code enregistre 6
, 6 fois:
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
Mais ce code ...
(function timer() {
for (let i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
... enregistre le résultat suivant:
0
1
2
3
4
5
Pourquoi?
Est-ce parce que let
se lie à l'étendue interne de chaque élément différemment et var
conserve la dernière valeur de i
?
Avec var
vous avez une portée de fonction, et une seule liaison partagée pour toutes vos itérations de boucle - c'est-à-dire le i
dans chaque rappel setTimeout signifie la même variable que enfin est égale à 6 après la fin de l'itération de la boucle.
Avec let
vous avez une portée de bloc et lorsqu'il est utilisé dans la boucle for
vous obtenez une nouvelle liaison pour chaque itération - c'est-à-dire que le i
dans chaque rappel setTimeout signifie une variable différente , chacune ayant une valeur différente: la première est 0, le suivant est 1 etc.
Donc ça:
(function timer() {
for (let i = 0; i <= 5; i++) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}
})();
est équivalent à ceci en utilisant uniquement var:
(function timer() {
for (var j = 0; j <= 5; j++) {
(function () {
var i = j;
setTimeout(function clog() { console.log(i); }, i * 1000);
}());
}
})();
en utilisant l'expression de fonction immédiatement invoquée pour utiliser la portée de la fonction de la même manière que la portée du bloc fonctionne dans l'exemple avec let
.
Il pourrait être écrit plus court sans utiliser le nom j
, mais ce ne serait peut-être pas aussi clair:
(function timer() {
for (var i = 0; i <= 5; i++) {
(function (i) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}(i));
}
})();
Et encore plus court avec les fonctions flèches:
(() => {
for (var i = 0; i <= 5; i++) {
(i => setTimeout(() => console.log(i), i * 1000))(i);
}
})();
(Mais si vous pouvez utiliser les fonctions fléchées, il n'y a aucune raison d'utiliser var
.)
Voici comment Babel.js traduit votre exemple avec let
pour s'exécuter dans des environnements où let
n'est pas disponible:
"use strict";
(function timer() {
var _loop = function (i) {
setTimeout(function clog() {
console.log(i);
}, i * 1000);
};
for (var i = 0; i <= 5; i++) {
_loop(i);
}
})();
Merci à Michael Geary d'avoir publié le lien vers Babel.js dans les commentaires. Voir le lien dans le commentaire pour une démonstration en direct où vous pouvez changer quoi que ce soit dans le code et regarder la traduction en cours immédiatement. Il est intéressant de voir comment les autres fonctionnalités d'ES6 sont également traduites.
Techniquement, c'est comme l'explique @rsp dans son excellente réponse. C'est ainsi que j'aime comprendre que les choses fonctionnent sous le capot. Pour le premier bloc de code utilisant var
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
Vous pouvez imaginer que le compilateur va comme ceci à l'intérieur de la boucle for
setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec
setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec
setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec
etc
puisque i
est déclaré à l'aide de var
, lorsque clog
est appelé, le compilateur trouve la variable i
dans le bloc fonctionnel le plus proche qui est timer
et puisque nous avons déjà atteint la fin de la boucle for
, i
contient la valeur 6 et exécutons clog
. Cela explique 6 étant connecté six fois.