zasięg var i let w pętli, closures

Odpowiedz Nowy wątek
2018-10-05 08:48

Rejestracja: 3 lata temu

Ostatnio: 1 tydzień temu

1

Szanowni!

Wszyscy wiemy co zwróci każda z tych dwóch pętli:

for (var i = 0; i < 4; i++) {
    setTimeout(function() {
    console.log(i)
    }, 3000)
}

for (let i = 0; i < 4; i++) {
    setTimeout(function() {
    console.log(i)
    }, 3000)
}

Z filmów tych szanownych gentleman'ów:
www.youtube.com/watch?v=-xqJo5VRP4A
www.youtube.com/watch?v=YvJY6z6Xwr4

Dowiedziałem się co nieco o domknięciach.
Wiem również jaki jest zasięg działania let i var (const też ;-) )

Nie potrafię jednak zrozumieć tego zasięgu w pętli.

Wyobrażam to sobie jakby var było deklarowane przed pętlą i potem za każdym "obrotem" była przypisywana do zmiennej inna wartość.
A let zachowuje się trochę jakby było deklarowane od nowa z każdą iteracją. (tak, wiem że tak się nie da)

Jest to z pewnością wyobrażenie błędne.

Czy byłby mi ktoś w stanie zilustrować prostymi słowami jak działa zasięg tych zmiennych w pętli?

Pozostało 580 znaków

2018-10-05 09:15
Moderator

Rejestracja: 13 lat temu

Ostatnio: 6 godzin temu

Lokalizacja: Wrocław

1

W gruncie rzeczy dobrze rozumiesz - var powoduje hoisting zmiennej, więc na przykład:

for (var i = 0; i < 10; ++i);

console.log(i); // wyświetli `10`, ponieważ deklaracja zmiennej `i` została wyniesiona przed pętlę
for (let i = 0; i < 10; ++i);

console.log(i); // wyświetli `i is not defined`, ponieważ czas życia zmiennej `i` obejmuje wyłącznie pętlę

Ale już:

let i;

for (i = 0; i < 10; ++i);

console.log(i); // wyświetli: 10

Wracając do Twoich przykładów - jako że w drugim przypadku (z wykorzystaniem let) i jest żywe tylko wewnątrz danej iteracji pętli, tworzone domknięcie jest wiązane właśnie z daną wartością i.

Gdybyśmy nieco ten przykład przerobili (dokonali takiego ręcznego hoistingu):

let i;

for (i = 0; i < 4; ++i) {
  setTimeout(function() {
    console.log(i);
  }, 3000);
}

... to już wszystkie cztery funkcje wyświetlą 4, ponieważ zostaną związane ze zmienną i z wyższego scope'u (spoza samej pętli). która będzie miała wtedy taką właśnie wartość.


edytowany 1x, ostatnio: Patryk27, 2018-10-05 09:16

Pozostało 580 znaków

2018-10-05 09:16

Rejestracja: 17 lat temu

Ostatnio: 8 godzin temu

Lokalizacja: Kraków

0

Twoje wyobrażenie jest jak najbardziej prawidłowe, var tworzy pojedyńczy binding "storage space", a let i const dla każdej iteracji mają nowy binding.


It's easy to hate code you didn't write, without an understanding of the context in which it was written.

Pozostało 580 znaków

Zimny Młot
2018-10-05 12:23
Zimny Młot
0

tak poza konkursem spytam, dlatego tu uzyta jest function()?

for (let i = 0; i < 4; i++) {
    setTimeout(function() {
    console.log(i)
    }, 3000)
}

nie można tego zapisać po prostu

for (let i = 0; i < 4; i++) {
    setTimeout(console.log(i, 3000))
}

Pozostało 580 znaków

Zimny Młot
2018-10-05 12:24
Zimny Młot
0

zrobiła mi sie literówka z nawiasami w drugim ploku kodu

Pozostało 580 znaków

2018-10-05 12:33
Moderator

Rejestracja: 13 lat temu

Ostatnio: 6 godzin temu

Lokalizacja: Wrocław

0
setTimeout(console.log(...), 1000);
// ^ spowoduje uruchomienie `console.log()` *od razu*

setTimeout(function() { console.log(...) }, 1000);
// ^ spowoduje uruchomienie `console.log()` po sekundzie

edytowany 1x, ostatnio: Patryk27, 2018-10-05 12:33

Pozostało 580 znaków

Zimny Młot
2018-10-05 12:37
Zimny Młot
0

@Patryk27: dlaczego tak się dzieje? Funkcje mają jakąś odroczoną możliwośc wykonania, czy wynika to z czegoś innego?

Pozostało 580 znaków

2018-10-05 12:44
Moderator

Rejestracja: 13 lat temu

Ostatnio: 6 godzin temu

Lokalizacja: Wrocław

0

Wyobraź sobie coś takiego (https://ideone.com/WGtbHp):

funkcjaA(funkcjaB());

Naturalne jest, że funkcjaB() musi się wykonać i zwrócić jakąś wartość, która stanie się argumentem dla funkcji funkcjaA().

Rozważmy jednak następujący przypadek (https://ideone.com/qtbAld):

funkcjaA(funkcjaB);

Tutaj sytuacja jest zgoła inna - do funkcjaA() nie przekazujemy rezultatu działania funkcjaB(), tylko wskaźnik na tę funkcję.
Innymi słowy: zapis funkcjaA(funkcjaB); nie powoduje uruchomienia funkcji funkcjaB.

Zamień funkcjaA na setTimeout, funkcjaB na console.log i będziesz miał odpowiedź ;-)


edytowany 1x, ostatnio: Patryk27, 2018-10-05 12:47

Pozostało 580 znaków

2018-10-05 13:14

Rejestracja: 3 lata temu

Ostatnio: 1 tydzień temu

0

Jest jeszcze jeden przypadek którego nie rozumiem:
Funkcja

function logaj() {console.log(i)}

for (let i = 0; i < 5; i++){
    setTimeout(logaj, 3e3)
}

wyrzuca błąd że " i is not defined"
Dla czego? W końcu ta funkcja znajduję się w zasięgu zmiennej i ?

Pozostało 580 znaków

2018-10-05 13:18

Rejestracja: 4 lata temu

Ostatnio: 5 godzin temu

0

Najlepsza, uniwersalna reguła dotycząca var jest taka, żeby tego po prostu nie używać :)
Ps. można zrobić tak:

setTimeout( () => console.log(i), 3000);

Pozostało 580 znaków

2018-10-05 13:18
Moderator

Rejestracja: 13 lat temu

Ostatnio: 6 godzin temu

Lokalizacja: Wrocław

1

W tym wypadku logaj nie jest domknięciem, tylko zwyczajną funkcją, dlatego też nie widzi zmiennej i (ponieważ nie jest ona ani zmienną globalną, ani lokalną dla logaj, ani parametrem funkcji logaj).

Kontekst ma znaczenie tylko w przypadku domknięć (funkcji anonimowych).


edytowany 1x, ostatnio: Patryk27, 2018-10-05 13:19

Pozostało 580 znaków

Odpowiedz

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0