Deze code registreert 6
, 6 keer:
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
Maar deze code…
(function timer() {
for (let i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
… registreert het volgende resultaat:
0
1
2
3
4
5
Waarom?
Is het omdat let
elk item anders aan de binnenste scope bindt en var
de laatste waarde van i
behoudt?
Antwoord 1, autoriteit 100%
Met var
heb je een functiebereik en slechts één gedeelde binding voor al je lus-iteraties – dwz de i
in elke setTimeout callback betekent dezelfdevariabele die uiteindelijkgelijk is aan 6 nadat de lusiteratie eindigt.
Met let
heb je een blokbereik en bij gebruik in de for
-lus krijg je een nieuwe binding voor elke iteratie – dwz de i
in elke setTimeout callback betekent een anderevariabele, die elk een andere waarde hebben: de eerste is 0, de volgende is 1 enz.
Dus dit:
(function timer() {
for (let i = 0; i <= 5; i++) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}
})();
is hier gelijk aan met alleen var:
(function timer() {
for (var j = 0; j <= 5; j++) {
(function () {
var i = j;
setTimeout(function clog() { console.log(i); }, i * 1000);
}());
}
})();
onmiddellijk aangeroepen functie-expressie gebruiken om functiebereik op een vergelijkbare manier te gebruiken als het blokbereik in het voorbeeld met let
werkt.
Het zou korter kunnen worden geschreven zonder de naam j
te gebruiken, maar misschien zou het niet zo duidelijk zijn:
(function timer() {
for (var i = 0; i <= 5; i++) {
(function (i) {
setTimeout(function clog() { console.log(i); }, i * 1000);
}(i));
}
})();
En nog korter met pijlfuncties:
(() => {
for (var i = 0; i <= 5; i++) {
(i => setTimeout(() => console.log(i), i * 1000))(i);
}
})();
(Maar als u pijlfuncties kunt gebruiken, is er geen reden om var
te gebruiken.)
Zo vertaalt Babel.js uw voorbeeld met let
om te draaien in omgevingen waar let
niet beschikbaar is:
"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);
}
})();
Met dank aan Michael Gearyvoor het plaatsen van de link naar Babel.js in de reacties. Zie de link in de opmerking voor een live demo waar je alles in de code kunt veranderen en de vertaling direct kunt zien plaatsvinden. Het is interessant om te zien hoe andere ES6-functies ook worden vertaald.
Antwoord 2, autoriteit 13%
Technisch is het hoe @rsp uitlegt in zijn uitstekende antwoord. Dit is hoe ik graag begrijp dat dingen onder de motorkap werken. Voor het eerste codeblok met var
(function timer() {
for (var i=0; i<=5; i++) {
setTimeout(function clog() {console.log(i)}, i*1000);
}
})();
Je kunt je voorstellen dat de compiler zo gaat in de for-lus
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
en ga zo maar door
aangezien i
is gedeclareerd met var
, vindt de compiler de variabele i
in wanneer i
wordt aangeroepen het dichtstbijzijnde functieblok dat timer
is en aangezien we het einde van de for
-lus al hebben bereikt, heeft i
de waarde 6 en voert u clog
. Dat verklaart dat 6 zes keer is ingelogd.