Witajcie, dzisiaj i mnie przyszło walczyć z tym problemem o szukać odpowiedzi. Mimo że pracuję z JS już od lat to dałem się złapać w tę pułapkę. A jaki mi się zdarza coś takiego to lubię to "zgłębić dogłębnie"
Przy okazji zaprezentuję inne rozwiązanie problemu niż bindowanie zmiennej do propertisu obiektu, gdyż w moim przypadku nie chodzi o event handler i nie ma żadnego obiektu. W moim konkretnym przypadku akurat odraczam wykonywanie pewnych czynności na czas po wykonaniu innych czynności (za pomocą domknięć), ale żeby nie komplikować można takie odroczenie zaprezentować za pomocą timeoutu.
Kod bazowy który nie działa:
var o = {one: 1, two: 2, three: 3};
var myfunc = function(p) {
alert(p);
}
var i = 500;
for (n in o) {
setTimeout(function() {myfunc(n)}, i);
i+=500;
}
Wydawało by się że dostaniemy trzy alerty: "one", "two", "three"; a dostajemy trzy alerty "three".
Teraz poprawka, która "naprawia" kod u góry:
for (n in o) {
function makeClosure(m) {
setTimeout(function() {myfunc(m)}, i);
};
makeClosure(n);
i+=500;
}
Czyli sposób rozwiązania problemu - owinąć nasz kod korzystający ze zmiennej w pętli w inną funkcję.
I teraz pytanko, korro napisał "Dlatego, że zmienne bindowane są w momencie użycia, a nie deklaracji. " co nie do końca rozumiem. Oto jak rozumiem ten problem:
- pętla nie tworzy nowego scope zatem w momencie tworzenia funkcji anonimowej w timeoucie wykorzystywany jest bieżący scope na którym leży nasza zmienna "n"
- przy każdym wywołaniu funkcji, korzystamy z naszego scopu ze zmienną "n" która wskazuje na ostatnią wartość "n" dlatego, że nie nigdy był stworzony nowy obiekt scope, tylko aktualizowany bieżący scope aktualną wartością "n"
- dodanie funkcji owijającej nasz wrażliwy kod spowodowało stworzenie nowego scope dla każdego wywołania funkcji anonimowej, skąd dostępna jest nowa zmienna "m"
Jeśli tak jest, to błąd myślowy jaki popełniłem tutaj jest taki, że założyłem że nowy scope chain tworzony jest w momencie tworzenia nowej funkcji (w sensie stworzenia wszystkich obiektów w chainie), natomiast w rzeczywistości w kontekście wyżej istnieje już nadrzędny scope chain który jest wykorzystany dla kontekstu nowej funkcji (w sensie wykorzystania tych samych instancji scope) i tylko dodawana jest do niego nowo tworzona instacja scope dla nowego kontekstu.
Czy tak jest w rzeczywistości?