Jakiś czas temu miałem podobne wątpliwości. W sposób abstrakcyjny próbowałem napisać kod robiący to samo.
Gdyby zadeklarowana została dodatkowa zmienna zużyciem let wewnątrz bloku funkcji, to wyświetlone zostaną liczby 0, 1, 2. Wydaje mi się, że na podobnej zasadzie działa pętla z deklaracją licznika pętli za pomocą let.
Kopiuj
for (var i = 0; i < 3; i++) {
let j = i;
setTimeout(function() {
console.log(j);
}, 0);
}
Taki kod można zobrazować w taki sposób:
Kopiuj
{
let j = 0;
setTimeout(function() {
console.log(j);
}, 0);
}
{
let j = 1;
setTimeout(function() {
console.log(j);
}, 0);
}
{
let j = 2;
setTimeout(function() {
console.log(j);
}, 0);
}
Wykonanie funkcji console.log przez funkcję setTimeout powoduje, że kod wyświetlający licznik pętli wykonuje się asynchronicznie, po ukończeniu działania wszystkich synchronicznych instrukcji do wykonania - w tym całej pętli. To sprawia, że licznik pętli, który jest domknięciem, ma już wartość docelową po ukończeniu działania pętli. Jeśli jest użyte var, to zmienna została zadeklarowana raz w zasięgu całej funkcji zawierającej pętlę lub w zasięgu globalnym, a w każdej kolejnej iteracji pętli ponowna deklaracja jest ignorowana, dlatego zmienna ma wartość nadaną jej po ostatniej iteracji. Jeśli jest użyte słowo let, to zmienna została zadeklarowana w bloku pętli, dlatego jej wartości dla kolejnych iteracji są różne po ukończeniu działania pętli.
To samo w prostszy sposób bez użycia setTimeout można pokazać w taki sposób:
Kopiuj
let arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function () {
return i;
});
}
arr.forEach(function (element) {
var result = element();
console.log(result);
});
Podejrzewam, że na tej samej zasadzie odbywa się to w pętli zdarzeń, do której setTimeout dodaje funkcję po upływie określonego czasu.
tak poza konkursem spytam, dlatego tu uzyta jest function()?
Kopiuj
for (let i = 0; i < 4; i++) {
setTimeout(function() {
console.log(i)
}, 3000)
}
nie można tego zapisać po prostu
Kopiuj
for (let i = 0; i < 4; i++) {
setTimeout(console.log(i, 3000))
}
setTimeout przyjmuje parametr, który jest funkcją (wywołanie zwrotne), która zostaje wywołana po upływie określonego czasu. console.log(i, 3000) nie jest funkcją, tylko wynikiem tej funkcji, czyli w tym przypadku undefined. Gdybyś chciał wykonać to w taki sposób, jak w Twoim pytaniu, to musiałbyś napisać setTimeout(console.log, 3000), a to nie miałoby sensu, bo nie przekazujesz argumentów do wyświetlenia. Zadziałałoby to, gdybyś napisał setTimeout(console.log.bind(null, i), 3000).
Większość została wytłumaczona już wcześniej, ale tutaj przedstawiłem to z własnymi przykładami i wnioskami.