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.
setTimeout
też przyjmuje parametry do przekazania wywołaniu zwrotnemu:setTimeout(console.log, 3000, i)
.