var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
Wyprowadza to:
Moja wartość: 3
Moja wartość: 3
Moja wartość: 3
Podczas gdy chciałbym, aby generowało:
Moja wartość: 0
Moja wartość: 1
Moja wartość: 2
Ten sam problem występuje, gdy opóźnienie w uruchomieniu funkcji jest spowodowane użyciem detektorów zdarzeń:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
… Lub kod asynchroniczny, np. Używając Promises:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
Edit: Jest to również widoczne w for in
i for of
pętle:
const arr = [1,2,3];
const fns = [];
for(i in arr){
fns.push(() => console.log(`index: ${i}`));
}
for(v of arr){
fns.push(() => console.log(`value: ${v}`));
}
for(f of fns){
f();
}
Jakie jest rozwiązanie tego podstawowego problemu?
javascript
loops
closures
pseudonim
źródło
źródło
funcs
być tablicą, jeśli używasz indeksów numerycznych? Tylko jedna głowa do góry.Odpowiedzi:
Problem polega na tym, że zmienna
i
w obrębie każdej z twoich anonimowych funkcji jest powiązana z tą samą zmienną poza funkcją.Klasyczne rozwiązanie: zamknięcia
To, co chcesz zrobić, to powiązać zmienną w ramach każdej funkcji z osobną, niezmienną wartością poza funkcją:
Ponieważ w JavaScript nie ma zakresu bloków - tylko zakres funkcji - zawijając tworzenie funkcji w nowej funkcji, upewniasz się, że wartość „i” pozostaje zgodna z przeznaczeniem.
Rozwiązanie ES5.1: forEach
Przy stosunkowo powszechnej dostępności
Array.prototype.forEach
funkcji (w 2015 r.) Warto zauważyć, że w sytuacjach, w których iteracja dotyczy przede wszystkim szeregu wartości,.forEach()
zapewnia czysty, naturalny sposób uzyskania wyraźnego zamknięcia każdej iteracji. Oznacza to, że zakładając, że masz jakąś tablicę zawierającą wartości (odniesienia DOM, obiekty, cokolwiek), a powstanie problem z ustawieniem wywołań zwrotnych specyficznych dla każdego elementu, możesz to zrobić:Chodzi o to, że każde wywołanie funkcji zwrotnej używanej z
.forEach
pętlą będzie miało własne zamknięcie. Parametrem przekazanym do tej procedury obsługi jest element tablicy specyficzny dla tego konkretnego kroku iteracji. Jeśli zostanie użyty w asynchronicznym wywołaniu zwrotnym, nie zderzy się z żadnym innym wywołaniem zwrotnym ustanowionym na innych etapach iteracji.Jeśli akurat pracujesz w jQuery,
$.each()
funkcja daje podobne możliwości.Rozwiązanie ES6:
let
ECMAScript 6 (ES6) wprowadza nowe
let
iconst
słowa kluczowe, których zakres różni się odvar
zmiennych opartych na zmiennych. Na przykład w pętli zlet
indeksem bazowym każda iteracja przez pętlę będzie miała nową zmiennąi
o zasięgu pętli, więc kod będzie działał zgodnie z oczekiwaniami. Istnieje wiele zasobów, ale polecam post z zakresu bloków 2ality jako świetne źródło informacji.Uważaj jednak, że IE9-IE11 i Edge wcześniejsze niż Edge 14 obsługują
let
powyższe błędy, ale źle to robią (nie tworzą nowego zai
każdym razem, więc wszystkie powyższe funkcje rejestrowałyby 3 tak, jak gdybyśmy tego użylivar
). Edge 14 w końcu to dobrze robi.źródło
function createfunc(i) { return function() { console.log("My value: " + i); }; }
jeszcze zamknięciem, ponieważ używa zmienneji
?Function.bind()
do tej pory zdecydowanie lepiej jest używać , patrz stackoverflow.com/a/19323214/785541 ..bind()
brzmi „poprawna odpowiedź”, jest nieprawidłowa. Każdy ma swoje miejsce. Nie.bind()
można wiązać argumentów bez wiązaniathis
wartości. Otrzymujesz także kopięi
argumentu bez możliwości mutowania go między wywołaniami, co czasami jest potrzebne. Są to więc zupełnie inne konstrukcje, nie wspominając o tym, że.bind()
implementacje były historycznie powolne. Oczywiście w prostym przykładzie albo zadziałałoby, ale zamknięcia są ważną koncepcją do zrozumienia i o to właśnie chodziło.Próbować:
Edycja (2014):
Osobiście myślę, że @ Aust's najnowsza odpowiedź temat używania
.bind
jest najlepszym sposobem na zrobienie tego rodzaju rzeczy teraz. Istnieje również wersja lo-kreska / podkreślenia jest_.partial
, kiedy nie potrzebują lub chcą zadzierać zbind
„sthisArg
.źródło
}(i));
?i
jako argumentindex
funkcji.Innym sposobem, o którym jeszcze nie wspomniano, jest użycie
Function.prototype.bind
AKTUALIZACJA
Jak wskazują @squint i @mekdev, lepszą wydajność uzyskuje się, tworząc najpierw funkcję poza pętlą, a następnie wiążąc wyniki w pętli.
źródło
_.partial
.bind()
będzie w dużej mierze przestarzałe dzięki funkcjom ECMAScript 6. Poza tym tworzy to dwie funkcje na iterację. Najpierw anonimowy, potem wygenerowany przez.bind()
. Lepszym rozwiązaniem byłoby utworzenie go poza pętlą, a następnie w.bind()
środku.bind
jest używany. Dodałem inny przykład do twoich sugestii.this
.Za pomocą wyrażenia funkcji natychmiast wywołanego , najprostszy i najbardziej czytelny sposób na zamknięcie zmiennej indeksu:
To wysyła iterator
i
do anonimowej funkcji, którą definiujemy jakoindex
. Tworzy to zamknięcie, w którym zmiennai
zostaje zapisana do późniejszego wykorzystania w dowolnej funkcji asynchronicznej w IIFE.źródło
i
tego, co to jest, zmienię nazwę parametru funkcji naindex
.index
zamiasti
.var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
.forEach()
, ale przez większość czasu, gdy zaczyna się od tablicy,forEach()
jest to dobry wybór, na przykład:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
Trochę za późno na imprezę, ale dzisiaj zgłębiałem ten problem i zauważyłem, że wiele odpowiedzi nie do końca odnosi się do tego, jak JavaScript traktuje zakresy, co w gruncie rzeczy sprowadza się do tego.
Tak jak wielu innych wspomniano, problemem jest to, że funkcja wewnętrzna odnosi się do tej samej
i
zmiennej. Dlaczego więc po prostu nie tworzymy nowej zmiennej lokalnej przy każdej iteracji i zamiast tego mamy odwołanie do funkcji wewnętrznej?Tak jak poprzednio, gdzie każda funkcja wewnętrzna generowała ostatnią przypisaną wartość
i
, teraz każda funkcja wewnętrzna tylko wyświetla ostatnią przypisaną wartośćilocal
. Ale nie każda iteracja powinna mieć swoją własnąilocal
?Okazuje się, że to jest problem. Każda iteracja ma ten sam zakres, więc każda iteracja po pierwszej jest po prostu nadpisywana
ilocal
. Z MDN :Podkreślono dla podkreślenia:
Możemy to sprawdzić, sprawdzając,
ilocal
zanim zadeklarujemy to w każdej iteracji:Właśnie dlatego ten błąd jest tak trudny. Nawet jeśli ponownie zmieniasz zmienną, JavaScript nie wyśle błędu, a JSLint nawet nie wyśle ostrzeżenia. Dlatego też najlepszym sposobem rozwiązania tego jest skorzystanie z zamknięć, co jest w istocie ideą, że w Javascripcie funkcje wewnętrzne mają dostęp do zmiennych zewnętrznych, ponieważ wewnętrzne zakresy „otaczają” zewnętrzne zakresy.
Oznacza to również, że funkcje wewnętrzne „trzymają” zewnętrzne zmienne i utrzymują je przy życiu, nawet jeśli funkcja zewnętrzna powróci. Aby to wykorzystać, tworzymy i wywołujemy funkcję otoki wyłącznie w celu utworzenia nowego zakresu, zadeklarowania
ilocal
w nowym zakresie i zwrócenia używanej funkcji wewnętrznejilocal
(więcej wyjaśnień poniżej):Utworzenie funkcji wewnętrznej w funkcji opakowania daje funkcji wewnętrznej prywatne środowisko, do którego tylko on może uzyskać dostęp, „zamknięcie”. Dlatego za każdym razem, gdy wywołujemy funkcję otoki, tworzymy nową funkcję wewnętrzną z własnym oddzielnym środowiskiem, zapewniając, że
ilocal
zmienne się nie kolidują i nie nadpisują. Kilka drobnych optymalizacji daje ostateczną odpowiedź, którą podało wielu innych użytkowników SO:Aktualizacja
Dzięki ES6 mainstream możemy teraz używać nowego
let
słowa kluczowego do tworzenia zmiennych o zasięgu blokowym:Zobacz, jakie to jest teraz łatwe! Aby uzyskać więcej informacji, zobacz tę odpowiedź , na której oparte są moje informacje.
źródło
let
iconst
. Gdyby tę odpowiedź rozszerzyć o to, moim zdaniem byłoby to o wiele bardziej przydatne na całym świecie.let
ii=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }
? (może zastąpić window.open () przez getelementbyid ......)i
funkcji limitu czasu, więc nie potrzebujesz zamknięciaDzięki obecnie szeroko wspieranemu ES6 najlepsza odpowiedź na to pytanie uległa zmianie. ES6 zapewnia słowa kluczowe
let
iconst
dla tej dokładnej okoliczności. Zamiast bawić się zamknięciami, możemy po prostulet
ustawić zmienną zakresu pętli w następujący sposób:val
wskaże wówczas obiekt specyficzny dla tego konkretnego obrotu pętli i zwróci prawidłową wartość bez dodatkowego zapisu zamknięcia. To oczywiście znacznie upraszcza ten problem.const
jest podobny dolet
dodatkowego ograniczenia, że nazwa zmiennej nie może zostać ponownie powiązana z nowym odwołaniem po początkowym przypisaniu.Obsługa przeglądarek jest teraz dostępna dla tych, którzy celują w najnowsze wersje przeglądarek.
const
/let
są obecnie obsługiwane w najnowszych przeglądarkach Firefox, Safari, Edge i Chrome. Jest również obsługiwany w węźle i można go używać w dowolnym miejscu, korzystając z narzędzi do budowania, takich jak Babel. Możesz zobaczyć działający przykład tutaj: http://jsfiddle.net/ben336/rbU4t/2/Dokumenty tutaj:
Uważaj jednak, że IE9-IE11 i Edge wcześniejsze niż Edge 14 obsługują
let
powyższe błędy, ale źle to robią (nie tworzą nowego zai
każdym razem, więc wszystkie powyższe funkcje rejestrowałyby 3 tak, jak gdybyśmy tego użylivar
). Edge 14 w końcu to dobrze robi.źródło
Innym sposobem powiedzenia jest to, że
i
funkcja jest związana w momencie wykonywania funkcji, a nie w momencie jej tworzenia.Podczas tworzenia zamknięcia
i
jest odwołaniem do zmiennej zdefiniowanej w zakresie zewnętrznym, a nie jej kopią z okresu, w którym utworzono zamknięcie. Zostanie to ocenione w momencie wykonania.Większość pozostałych odpowiedzi zapewnia sposoby obejścia problemu poprzez utworzenie innej zmiennej, która nie zmieni wartości dla Ciebie.
Pomyślałem, że dodam wyjaśnienie dla jasności. Dla rozwiązania osobiście wybrałbym rozwiązanie Harto, ponieważ jest to najbardziej oczywisty sposób na zrobienie tego z odpowiedzi tutaj. Dowolny opublikowany kod będzie działał, ale wybrałbym fabrykę zamykania zamiast pisania stosu komentarzy, aby wyjaśnić, dlaczego ogłaszam nową zmienną (Freddy i 1800) lub dziwnie osadzoną składnię zamknięcia (apphacker).
źródło
To, co musisz zrozumieć, to zakres zmiennych w javascript oparty na funkcji. Jest to ważna różnica niż powiedzmy c #, gdzie masz zakres blokowy, a po prostu skopiowanie zmiennej do zmiennej wewnątrz for będzie działać.
Zawinięcie go w funkcję, która ocenia zwracanie funkcji, tak jak odpowiedź apphackera, załatwi sprawę, ponieważ zmienna ma teraz zasięg funkcji.
Istnieje także słowo kluczowe let zamiast var, które pozwoliłoby na użycie reguły zasięgu bloku. W takim przypadku zdefiniowanie zmiennej wewnątrz for by załatwiło sprawę. To powiedziawszy, słowo kluczowe let nie jest praktycznym rozwiązaniem ze względu na kompatybilność.
źródło
Oto kolejna odmiana techniki, podobna do Bjorna (apphacker), która pozwala przypisać wartość zmiennej do funkcji zamiast przekazywać ją jako parametr, co czasem może być jaśniejsze:
Zauważ, że niezależnie od zastosowanej techniki
index
zmienna staje się rodzajem zmiennej statycznej, związanej ze zwróconą kopią funkcji wewnętrznej. To znaczy zmiany wartości są zachowywane między połączeniami. Może być bardzo przydatny.źródło
var
linii ireturn
linia nie działałaby? Dzięki!var
areturn
następnie zmienna nie zostanie przypisana, zanim zwróci funkcję wewnętrzną.Opisuje to powszechny błąd związany z używaniem zamknięć w JavaScript.
Funkcja definiuje nowe środowisko
Rozważać:
Przy każdym
makeCounter
wywołaniu{counter: 0}
powoduje utworzenie nowego obiektu. Tworzona jest również nowa kopia pliku,obj
aby odwoływać się do nowego obiektu. Tak więccounter1
icounter2
są od siebie niezależni.Zamknięcia w pętlach
Użycie zamknięcia w pętli jest trudne.
Rozważać:
Zauważ, że
counters[0]
icounters[1]
to nie niezależne. W rzeczywistości działają na tym samymobj
!Jest tak, ponieważ istnieje tylko jedna kopia
obj
współdzielona we wszystkich iteracjach pętli, być może ze względu na wydajność. Mimo że{counter: 0}
w każdej iteracji tworzy nowy obiekt, ta sama kopiaobj
zostanie zaktualizowana o odniesienie do najnowszego obiektu.Rozwiązaniem jest użycie innej funkcji pomocnika:
Działa to, ponieważ zmienne lokalne w zakresie funkcji bezpośrednio, a także zmienne argumentów funkcji, otrzymują nowe kopie po wejściu.
źródło
var obj = {counter: 0};
jest analizowany przed wykonaniem jakiegokolwiek kodu, jak podano w: MDN var : var deklaracje, gdziekolwiek występują, są przetwarzane przed wykonaniem jakiegokolwiek kodu.Najprostszym rozwiązaniem byłoby
Zamiast używać:
który ostrzega „2” 3 razy. Wynika to z faktu, że anonimowe funkcje utworzone w pętli dzielą to samo zamknięcie, aw tym zamknięciu wartość
i
jest taka sama. Użyj tego, aby zapobiec wspólnemu zamknięciu:Ideą tego jest otoczenie całego ciała pętli for za pomocą IIFE (Natychmiastowe wywołanie wyrażenia funkcji) i przekazanie go
new_i
jako parametru i przechwycenie go jakoi
. Ponieważ anonimowa funkcja jest wykonywana natychmiast,i
wartość jest inna dla każdej funkcji zdefiniowanej w funkcji anonimowej.To rozwiązanie wydaje się pasować do każdego takiego problemu, ponieważ będzie wymagało minimalnych zmian w oryginalnym kodzie cierpiącym na ten problem. W rzeczywistości jest to zgodne z projektem, nie powinno to wcale stanowić problemu!
źródło
spróbuj tego krótszego
bez tablicy
bez dodatkowych pętli
http://jsfiddle.net/7P6EN/
źródło
Oto proste rozwiązanie, które wykorzystuje
forEach
(działa z powrotem do IE9):Wydruki:
źródło
Głównym problemem z kodem pokazanym przez OP jest to, że
i
nigdy nie jest odczytywany aż do drugiej pętli. Aby to zademonstrować, wyobraź sobie, że widzisz błąd w kodzieBłąd faktycznie nie występuje, dopóki nie
funcs[someIndex]
zostanie wykonany()
. Korzystając z tej samej logiki, powinno być oczywiste, że wartość parametrui
również nie jest gromadzona aż do tego momentu. Gdy oryginalna pętla zakończy się,i++
doprowadzai
do wartości,3
która powodujei < 3
awarię warunku i zakończenie pętli. W tym momenciei
jest3
i kiedyfuncs[someIndex]()
jest używany, orazi
jest oceniany, wynosi 3 - za każdym razem.Aby temu zaradzić, musisz ocenić,
i
jak się napotyka. Pamiętaj, że stało się to już w formiefuncs[i]
(gdzie istnieją 3 unikalne indeksy). Istnieje kilka sposobów na uchwycenie tej wartości. Jednym z nich jest przekazanie go jako parametru do funkcji, która jest pokazana na kilka sposobów już tutaj.Inną opcją jest zbudowanie obiektu funkcji, który będzie mógł zamknąć zmienną. Można to w ten sposób osiągnąć
jsFiddle Demo
źródło
Funkcje JavaScript „zamykają” zakres, do którego mają dostęp po deklaracji i zachowują dostęp do tego zakresu, nawet gdy zmienne w tym zakresie się zmieniają.
Każda funkcja w powyższej tablicy zamyka się w zakresie globalnym (globalnym, po prostu dlatego, że jest to zakres, w którym zostały zadeklarowane).
Później wywoływane są te funkcje, które rejestrują najnowszą wartość
i
w zakresie globalnym. To magia i frustracja zamknięcia.„Funkcje JavaScript zamykają się w zakresie, w którym zostały zadeklarowane, i zachowują dostęp do tego zakresu, nawet gdy zmieniają się wartości zmiennych wewnątrz tego zakresu”.
Użycie
let
zamiast tegovar
rozwiązuje, tworząc nowy zakres za każdym razem, gdyfor
pętla się uruchamia, tworząc oddzielny zakres dla każdej funkcji do zamknięcia. Różne inne techniki robią to samo z dodatkowymi funkcjami.(
let
powoduje, że zmienne mają zakres blokowy. Bloki są oznaczone nawiasami klamrowymi, ale w przypadku pętli for zmienna inicjalizacyjna,i
w naszym przypadku, jest uważana za zadeklarowaną w nawiasach klamrowych).źródło
i
jest ustalana na skalę globalną. Pofor
zakończeniu działania pętli globalna wartośći
wynosi teraz 3. Dlatego za każdym razem, gdy ta funkcja jest wywoływana w tablicy (używając, powiedzmy,funcs[j]
), funkcjai
w tej funkcji odwołuje się doi
zmiennej globalnej (która jest 3).Po przeczytaniu różnych rozwiązań chciałbym dodać, że powodem ich działania jest poleganie na koncepcji łańcucha zasięgu . Jest to sposób, w jaki JavaScript rozpoznaje zmienną podczas wykonywania.
var
i jegoarguments
.window
.W kodzie początkowym:
Po
funcs
wykonaniu łańcuch zasięgu będziefunction inner -> global
. Ponieważ zmienneji
nie można znaleźć wfunction inner
(ani zadeklarowanej,var
ani przekazanej jako argumenty), kontynuuje wyszukiwanie, dopóki wartość parametru niei
zostanie ostatecznie znaleziona w zasięgu globalnym, który jestwindow.i
.Zawinięcie go w funkcję zewnętrzną albo wyraźnie zdefiniuj funkcję pomocnika, jak harto, albo użyj funkcji anonimowej, jak Bjorn :
Kiedy
funcs
zostanie wykonany, teraz będzie zasięg łańcuchafunction inner -> function outer
. Ten czasi
można znaleźć w zakresie funkcji zewnętrznej, która jest wykonywana 3 razy w pętli for, za każdym razemi
poprawnie przypisana wartość . Nie będzie używał wartościwindow.i
po wykonaniu wewnętrznym.Więcej szczegółów można znaleźć tutaj
Obejmuje to powszechny błąd w tworzeniu zamknięcia w pętli, co mamy tutaj, a także dlaczego potrzebujemy zamknięcia i rozważenia dotyczące wydajności.
źródło
Array.prototype.forEach(function callback(el) {})
naturalnie: Odwołanie zwrotne, które jest przekazywane w naturalny sposób, tworzy zakres zawijania z el poprawnie związanym w każdej iteracjiforEach
. Tak więc każda funkcja wewnętrzna zdefiniowana w wywołaniu zwrotnym będzie mogła użyć właściwejel
wartościDzięki nowym funkcjom zakresu blokowania ES6 zarządza się:
Kod w pytaniu OP zastępuje się
let
zamiastvar
.źródło
const
zapewnia ten sam wynik i należy go stosować, gdy wartość zmiennej się nie zmieni. Jednak użycieconst
wewnątrz inicjalizatora pętli for jest niepoprawnie zaimplementowane w przeglądarce Firefox i nie zostało jeszcze naprawione. Zamiast deklarować wewnątrz bloku, deklaruje się go poza blokiem, co powoduje redeklarację zmiennej, co z kolei powoduje błąd. Użycielet
wewnątrz inicjatora jest poprawnie zaimplementowane w Firefoksie, więc nie musisz się tam martwić.Dziwi mnie, że nikt jeszcze nie zasugerował użycia tej
forEach
funkcji, aby lepiej unikać (ponownie) używania zmiennych lokalnych. W rzeczywistości nie używamfor(var i ...)
już z tego powodu.// edytowane do użycia
forEach
zamiast mapy.źródło
.forEach()
jest znacznie lepszą opcją, jeśli tak naprawdę niczego nie mapujesz, a Daryl zasugerował, że 7 miesięcy przed opublikowaniem, więc nie ma się czym dziwić.To pytanie naprawdę pokazuje historię JavaScript! Teraz możemy uniknąć skalowania bloków za pomocą funkcji strzałek i obsługiwać pętle bezpośrednio z węzłów DOM przy użyciu metod Object.
źródło
Pierwotny przykład nie działał, ponieważ wszystkie zamknięcia utworzone w pętli odnosiły się do tej samej ramki. W efekcie posiadanie 3 metod na jednym obiekcie z tylko jedną
i
zmienną. Wszystkie wydrukowały tę samą wartość.źródło
Przede wszystkim zrozum, co jest nie tak z tym kodem:
Tutaj, gdy
funcs[]
tablica jest inicjowana,i
jest zwiększana,funcs
tablica jest inicjalizowana, a rozmiarfunc
tablicy staje się 3, więci = 3,
. Teraz, gdyfuncs[j]()
jest wywoływane, ponownie używa zmienneji
, która została już zwiększona do 3.Teraz, aby rozwiązać ten problem, mamy wiele opcji. Poniżej dwa z nich:
Możemy zainicjować
i
zlet
lub zainicjować nową zmiennąindex
zlet
i uczynić go równymi
. Kiedy połączenie zostanie wykonane,index
zostanie użyte, a jego zakres zakończy się po inicjalizacji. I dla połączeńindex
zostanie ponownie zainicjowany:Inną opcją może być wprowadzenie
tempFunc
zwracającej rzeczywistą funkcję:źródło
Użyj struktury zamknięcia , zmniejszy to dodatkową pętlę for. Możesz to zrobić w jednej pętli for:
źródło
Przypadek 1 : za pomocą
var
Teraz otwórz okno konsoli chrome , naciskając F12 i odśwież stronę. Rozwijaj co 3 funkcje w tablicy. Zobaczysz właściwość o nazwie
[[Scopes]]
.Rozwiń tę. Zobaczysz jeden obiekt tablicy o nazwie"Global"
, rozwiń go. Znajdziesz w obiekcie'i'
zadeklarowaną właściwość o wartości 3.Wniosek:
'var'
pomocą funkcji spoza funkcji, staje się zmienną globalną (możesz to sprawdzić, wpisująci
lubwindow.i
w oknie konsoli. Zwróci 3).console.log("My value: " + i)
pobiera wartość zGlobal
obiektu i wyświetla wynik.PRZYPADEK 2: użycie let
Teraz wymienić
'var'
z'let'
Zrób to samo, przejdź do lunety. Teraz zobaczysz dwa obiekty
"Block"
i"Global"
. Teraz rozwińBlock
obiekt, zobaczysz, że jest tam zdefiniowane „i”, a dziwne jest to, że dla każdej funkcji wartość ifi
jest różna (0, 1, 2).Wniosek:
Kiedy zadeklarujesz zmienną, używając
'let'
nawet poza funkcją, ale wewnątrz pętli, ta zmienna nie będzie zmienną globalną, stanie sięBlock
zmienną poziomu, która jest dostępna tylko dla tej samej funkcji. Z tego powodu otrzymujemy wartośći
różnej dla każdej funkcji, gdy wywołujemy funkcje.Aby uzyskać więcej informacji na temat tego, jak bliżej to działa, przejdź do niesamowitego samouczka wideo https://youtu.be/71AtaJpJHw0
źródło
Można użyć modułu deklaratywnego do list danych, takich jak query-js (*). W tych sytuacjach osobiście uważam, że deklaratywne podejście jest mniej zaskakujące
Następnie możesz użyć drugiej pętli i uzyskać oczekiwany wynik lub możesz to zrobić
(*) Jestem autorem zapytania-js i dlatego jestem skłonny do korzystania z niego, więc nie traktuj moich słów jako rekomendacji dla wspomnianej biblioteki tylko dla deklaratywnego podejścia :)
źródło
Query.range(0,3)
? To nie jest część tagów tego pytania. Poza tym, jeśli korzystasz z biblioteki strony trzeciej, możesz podać link do dokumentacji.Wolę używać
forEach
funkcji, która ma swoje własne zamknięcie z tworzeniem pseudo zakresu:To wygląda brzydiej niż zakresy w innych językach, ale IMHO jest mniej monstrualne niż inne rozwiązania.
źródło
I jeszcze jedno rozwiązanie: zamiast tworzyć kolejną pętlę, po prostu powiąż
this
funkcję return.Wiążąc to , rozwiązuje również problem.
źródło
Wiele rozwiązań wydaje się poprawnych, ale nie wspominają o nazwie,
Currying
która jest funkcjonalnym wzorcem projektowym programowania w sytuacjach takich jak tutaj. 3-10 razy szybszy niż bindowanie w zależności od przeglądarki.Zobacz wzrost wydajności w różnych przeglądarkach .
źródło
Twój kod nie działa, ponieważ działa:
Pytanie brzmi: jaka jest wartość zmiennej
i
przy wywołaniu funkcji? Ponieważ pierwsza pętla jest tworzona z warunkiemi < 3
, zatrzymuje się natychmiast, gdy warunek jest fałszywy, więc tak jesti = 3
.Musisz zrozumieć, że w czasie tworzenia funkcji żaden z ich kodów nie jest wykonywany, jest on zapisywany tylko na później. Kiedy więc zostaną wezwani później, interpreter wykonuje je i pyta: „Jaka jest obecna wartość
i
?”Twoim celem jest więc najpierw zapisanie wartości
i
funkcji, a dopiero potem zapisanie funkcji wfuncs
. Można to zrobić na przykład w następujący sposób:W ten sposób każda funkcja będzie miała swoją zmienną
x
i ustawiamyx
ją na wartośći
w każdej iteracji.To tylko jeden z wielu sposobów rozwiązania tego problemu.
źródło
źródło
Użyj let (block-scope) zamiast var.
źródło