Dlaczego powiązania let i var zachowują się inaczej używając funkcji setTimeout? [duplikat]

To pytanie ma już odpowiedź tutaj:

Ten kod loguje 6, 6 razy:

(function timer() {
  for (var i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

Ale ten kod...

(function timer() {
  for (let i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

... zapisuje następujący wynik:

0
1
2
3
4
5
Dlaczego?

Czy dlatego, że let wiąże się z wewnętrznym scope każdy element inaczej i var zachowuje ostatnią wartość i?

Author: Badacadabra, 2015-07-08

2 answers

Z var masz zakres funkcji i tylko jedno wspólne Wiązanie dla wszystkich iteracji pętli - tzn. i w każdym wywołaniu zwrotnym setTimeout oznacza tę samą zmienną, która W końcu jest równa 6 po zakończeniu iteracji pętli.

Z let masz zakres blokowy i gdy użyjesz pętli for, otrzymasz nowe Wiązanie dla każdej iteracji - tzn. i w każdym wywołaniu zwrotnym setTimeout oznacza inną zmienną, każda z która ma inną wartość: pierwsza to 0, następna to 1 itd.

Więc to:

(function timer() {
  for (let i = 0; i <= 5; i++) {
    setTimeout(function clog() { console.log(i); }, i * 1000);
  }
})();

Jest równoważne z tym używając tylko var:

(function timer() {
  for (var j = 0; j <= 5; j++) {
    (function () {
      var i = j;
      setTimeout(function clog() { console.log(i); }, i * 1000);
    }());
  }
})();

Użycie natychmiast wywołanego wyrażenia funkcji do użycia zakresu funkcji w podobny sposób, jak zakres bloku działa w przykładzie z let.

Mogłoby być napisane krócej bez użycia j nazwy, ale być może nie byłoby tak jasne:

(function timer() {
  for (var i = 0; i <= 5; i++) {
    (function (i) {
      setTimeout(function clog() { console.log(i); }, i * 1000);
    }(i));
  }
})();

I jeszcze krótszy ze strzałką funkcje:

(() => {
  for (var i = 0; i <= 5; i++) {
    (i => setTimeout(() => console.log(i), i * 1000))(i);
  }
})();

(ale jeśli możesz używać funkcji strzałek, nie ma powodu, aby używać var.)

Oto jak Babel.js tłumaczy twój przykład za pomocą let, aby działał w środowiskach, w których let nie jest dostępny:

"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);
  }
})();

Podziękowania dla Michaela Geary za umieszczenie linku do Babel.js w komentarzach. Zobacz link w komentarzu na żywo demo, gdzie można zmienić cokolwiek w kodzie i oglądać tłumaczenie odbywa się natychmiast. Ciekawe jak inne funkcje ES6 również tłumaczone.

 29
Author: rsp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-08 08:42:08

Technicznie tak wyjaśnia @rsp w swojej doskonałej odpowiedzi. Tak lubię rozumieć, że wszystko działa pod maską. Dla pierwszego bloku kodu używając var

(function timer() {
  for (var i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

Możesz sobie wyobrazić, że kompilator działa w ten sposób wewnątrz pętli 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

I tak dalej

Ponieważ i jest zadeklarowane za pomocą var, gdy wywołane jest clog, kompilator znajduje zmienną i w najbliższym bloku funkcji, który jest timer i ponieważ osiągnęliśmy już koniec pętli for, i przechowuje wartość 6 i wykonuje clog. To tłumaczy, że 6 było zalogowanych 6 razy.

 3
Author: Quannt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-08 07:56:42