Zamknięcie JavaScript wewnątrz pętli - prosty praktyczny przykład


2816

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 ini for ofpę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?


55
Na pewno nie chcesz funcsbyć tablicą, jeśli używasz indeksów numerycznych? Tylko jedna głowa do góry.
DanMan

23
To naprawdę mylący problem. Ten artykuł pomoże mi to zrozumieć . Może pomóc innym również.
user3199690

4
Kolejne proste i wyjaśnione rozwiązanie: 1) Funkcje zagnieżdżone mają dostęp do zakresu „nad nimi” ; 2) zamknięcie roztwór ... „Zamknięcie to funkcja dostępu do zakresu macierzystego nawet po funkcja macierzysty zamknięty”.
Peter Krauss,

2
Skorzystaj z tego linku, aby uzyskać lepsze informacje na temat nieporozumień javascript.info/tutorial/advanced-functions
Saurabh Ahuja

35
W ES6 trywialnym rozwiązaniem jest zadeklarowanie zmiennej i za pomocą let , której zakres obejmuje ciało pętli.
Tomas Nikodym

Odpowiedzi:


2146

Problem polega na tym, że zmienna iw 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ą:

var funcs = [];

function createfunc(i) {
  return function() {
    console.log("My value: " + i);
  };
}

for (var i = 0; i < 3; i++) {
  funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

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.forEachfunkcji (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ć:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

Chodzi o to, że każde wywołanie funkcji zwrotnej używanej z .forEachpę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 leti constsłowa kluczowe, których zakres różni się od varzmiennych opartych na zmiennych. Na przykład w pętli z letindeksem bazowym każda iteracja przez pętlę będzie miała nową zmienną io 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.

for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value: " + i);
  };
}

Uważaj jednak, że IE9-IE11 i Edge wcześniejsze niż Edge 14 obsługują letpowyższe błędy, ale źle to robią (nie tworzą nowego za ikażdym razem, więc wszystkie powyższe funkcje rejestrowałyby 3 tak, jak gdybyśmy tego użyli var). Edge 14 w końcu to dobrze robi.


7
nie jest function createfunc(i) { return function() { console.log("My value: " + i); }; }jeszcze zamknięciem, ponieważ używa zmiennej i?
ア レ ッ ク ス

55
Niestety, ta odpowiedź jest nieaktualna i nikt nie zobaczy poprawnej odpowiedzi na dole - Function.bind()do tej pory zdecydowanie lepiej jest używać , patrz stackoverflow.com/a/19323214/785541 .
Wladimir Palant

81
@Wladimir: Twoja sugestia, która .bind()brzmi „poprawna odpowiedź”, jest nieprawidłowa. Każdy ma swoje miejsce. Nie .bind()można wiązać argumentów bez wiązania thiswartości. Otrzymujesz także kopię iargumentu 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.
ciasteczkowy potwór

8
Przestań używać tych funkcji hacków for-return, zamiast tego użyj [] .forEach lub [] .map, ponieważ unikają ponownego użycia tych samych zmiennych zakresu.
Christian Landgren,

32
@ChristianLandgren: Przydaje się to tylko w przypadku iteracji tablicy. Te techniki nie są „hackami”. Są niezbędną wiedzą.

379

Próbować:

var funcs = [];
    
for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}

for (var j = 0; j < 3; j++) {
    funcs[j]();
}

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ć z bind„s thisArg.


2
jakieś wyjaśnienie na temat }(i));?
aswzen

3
@aswzen Myślę, że przekazuje ijako argument indexfunkcji.
Jet Blue

w rzeczywistości tworzy lokalny indeks zmiennych.
Abhishek Singh

1
Natychmiast wywołaj funkcję Expression, czyli IIFE. (i) jest argumentem dla anonimowego wyrażenia funkcji, które jest wywoływane natychmiast, a indeks jest ustawiany z i.
Jajka

348

Innym sposobem, o którym jeszcze nie wspomniano, jest użycie Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

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.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}


To właśnie robię w dzisiejszych czasach, lubię też lo-dash / podkreślenia_.partial
Bjorn

17
.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.

5
@squint @mekdev - Oboje macie rację. Mój początkowy przykład został napisany szybko, aby zademonstrować, w jaki sposób bindjest używany. Dodałem inny przykład do twoich sugestii.
Aust

5
Myślę, że zamiast marnować obliczenia na dwie pętle O (n), po prostu rób dla (var i = 0; i <3; i ++) {log.call (this, i); }
user2290820,

1
.bind () robi to, co sugeruje zaakceptowana odpowiedź this.
niry

269

Za pomocą wyrażenia funkcji natychmiast wywołanego , najprostszy i najbardziej czytelny sposób na zamknięcie zmiennej indeksu:

for (var i = 0; i < 3; i++) {

    (function(index) {

        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value:   $.ajax({});
    
    })(i);

}

To wysyła iterator ido anonimowej funkcji, którą definiujemy jako index. Tworzy to zamknięcie, w którym zmienna izostaje zapisana do późniejszego wykorzystania w dowolnej funkcji asynchronicznej w IIFE.


10
Aby uzyskać większą czytelność kodu i uniknąć pomyłek co do itego, co to jest, zmienię nazwę parametru funkcji na index.
Kyle Falconer,

5
Jak użyłbyś tej techniki do zdefiniowania funkcji tablicy opisanych w pierwotnym pytaniu?
Nico

@Nico Taki sam sposób, jak pokazano w pierwotnym pytaniu, z tym wyjątkiem, że użyłbyś indexzamiast i.
JLRishe,

@JLRishevar 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](); }
Nico

1
@Nico W konkretnym przypadku OP po prostu iterują liczby, więc nie byłby to świetny przypadek .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); }; });
JLRishe

164

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 izmiennej. Dlaczego więc po prostu nie tworzymy nowej zmiennej lokalnej przy każdej iteracji i zamiast tego mamy odwołanie do funkcji wewnętrznej?

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

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 :

Ważne: JavaScript nie ma zasięgu blokowego. Zmienne wprowadzone wraz z blokiem mają zakres obejmujący funkcję lub skrypt, a efekty ich ustawienia utrzymują się poza samym blokiem. Innymi słowy, instrukcje blokowe nie wprowadzają zakresu. Chociaż „samodzielne” bloki są poprawną składnią, nie chcesz używać samodzielnych bloków w JavaScript, ponieważ nie robią tego, co myślisz, że robią, jeśli uważasz, że robią coś takiego jak takie bloki w C lub Java.

Podkreślono dla podkreślenia:

JavaScript nie ma zasięgu blokowego. Zmienne wprowadzane za pomocą bloku mają zakres obejmujący funkcję lub skrypt

Możemy to sprawdzić, sprawdzając, ilocalzanim zadeklarujemy to w każdej iteracji:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

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.

Domknięcia

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 ilocalw nowym zakresie i zwrócenia używanej funkcji wewnętrznej ilocal(więcej wyjaśnień poniżej):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

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 ilocalzmienne się nie kolidują i nie nadpisują. Kilka drobnych optymalizacji daje ostateczną odpowiedź, którą podało wielu innych użytkowników SO:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

Aktualizacja

Dzięki ES6 mainstream możemy teraz używać nowego letsłowa kluczowego do tworzenia zmiennych o zasięgu blokowym:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

Zobacz, jakie to jest teraz łatwe! Aby uzyskać więcej informacji, zobacz tę odpowiedź , na której oparte są moje informacje.


Podoba mi się też, jak wyjaśniłeś sposób IIFE. Szukałem tego. Dziękuję Ci.
CapturedTree,

4
Istnieje teraz coś takiego jak określanie zakresu bloków w JavaScript za pomocą słów kluczowych leti const. Gdyby tę odpowiedź rozszerzyć o to, moim zdaniem byłoby to o wiele bardziej przydatne na całym świecie.

@ TinyGiant pewna rzecz, dodałem trochę informacji leti
podałem

@ woojoo666 Could odpowiedź również praca za nazwanie dwa przemienne URL w pętli tak: i=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 ......)
orzechowy o natty

@nuttyaboutnatty przepraszam za tak późną odpowiedź. Nie wygląda na to, że kod w twoim przykładzie już działa. Nie używasz ifunkcji limitu czasu, więc nie potrzebujesz zamknięcia
woojoo666,

151

Dzięki obecnie szeroko wspieranemu ES6 najlepsza odpowiedź na to pytanie uległa zmianie. ES6 zapewnia słowa kluczowe leti constdla tej dokładnej okoliczności. Zamiast bawić się zamknięciami, możemy po prostu letustawić zmienną zakresu pętli w następujący sposób:

var funcs = [];

for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

valwskaż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.

constjest podobny do letdodatkowego 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/ letsą 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ą letpowyższe błędy, ale źle to robią (nie tworzą nowego za ikażdym razem, więc wszystkie powyższe funkcje rejestrowałyby 3 tak, jak gdybyśmy tego użyli var). Edge 14 w końcu to dobrze robi.


Niestety opcja „let” nadal nie jest w pełni obsługiwana, szczególnie w urządzeniach mobilnych. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MattC

2
W czerwcu '16, let jest dostępna we wszystkich głównych wersji przeglądarek z wyjątkiem iOS Safari, Opera Mini i Safari 9. Evergreen przeglądarki obsługują go. Babel przetransponuje to poprawnie, aby utrzymać oczekiwane zachowanie bez włączonego trybu wysokiej zgodności.
Dan Pantry

@DanPantry tak o czas na aktualizację :) Zaktualizowano, aby lepiej odzwierciedlać obecny stan rzeczy, w tym dodając wzmiankę o const, linki do dokumentów i informacje o lepszej kompatybilności.
Ben McCormick

Czy nie dlatego używamy Babel do transponowania naszego kodu, aby przeglądarki, które nie obsługują ES6 / 7, mogły zrozumieć, co się dzieje?
piksel 67

87

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 ijest 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).


71

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ść.

var funcs = {};

for (var i = 0; i < 3; i++) {
  let index = i; //add this
  funcs[i] = function() {
    console.log("My value: " + index); //change to the copy
  };
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}


@nickf która przeglądarka? jak powiedziałem, ma problemy ze zgodnością, przez to mam na myśli poważne problemy ze zgodnością, tak jak nie sądzę, że let jest obsługiwany w IE.
eglasius

1
@nickf tak, sprawdź ten odnośnik: developer.mozilla.org/En/New_in_JavaScript_1.7 ... sprawdź sekcję Let Let Definitions, w pętli znajduje się przykład onclick
eglasius 16.04.2009

2
@nickf hmm, tak naprawdę musisz jawnie określić wersję: <script type = "application / javascript; version = 1.7" /> ... Właściwie nie użyłem go nigdzie z powodu ograniczeń IE, po prostu nie jest praktyczne :(
eglasius 16.04.2009

możesz zobaczyć obsługę przeglądarki dla różnych wersji tutaj es.wikipedia.org/wiki/Javascript
eglasius 16.04.2009


59

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:

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

Zauważ, że niezależnie od zastosowanej techniki indexzmienna 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.


Dzięki i twoje rozwiązanie działa. Ale chciałbym zapytać, dlaczego to działa, ale zamiana varlinii i returnlinia nie działałaby? Dzięki!
midnite

@midnite Jeśli podmieniłeś, vara returnnastępnie zmienna nie zostanie przypisana, zanim zwróci funkcję wewnętrzną.
Boann,

53

Opisuje to powszechny błąd związany z używaniem zamknięć w JavaScript.

Funkcja definiuje nowe środowisko

Rozważać:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

Przy każdym makeCounterwywołaniu {counter: 0}powoduje utworzenie nowego obiektu. Tworzona jest również nowa kopia pliku, obj aby odwoływać się do nowego obiektu. Tak więc counter1i counter2są od siebie niezależni.

Zamknięcia w pętlach

Użycie zamknięcia w pętli jest trudne.

Rozważać:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

Zauważ, że counters[0]i counters[1]to nie niezależne. W rzeczywistości działają na tym samym obj!

Jest tak, ponieważ istnieje tylko jedna kopia objwspół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 kopia objzostanie zaktualizowana o odniesienie do najnowszego obiektu.

Rozwiązaniem jest użycie innej funkcji pomocnika:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

Działa to, ponieważ zmienne lokalne w zakresie funkcji bezpośrednio, a także zmienne argumentów funkcji, otrzymują nowe kopie po wejściu.


Małe wyjaśnienie: W pierwszym przykładzie zamknięć w pętlach liczniki [0] i liczniki [1] nie są niezależne nie ze względu na wydajność. Powodem jest to, że 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.
Charidimos,

50

Najprostszym rozwiązaniem byłoby

Zamiast używać:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

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ść ijest taka sama. Użyj tego, aby zapobiec wspólnemu zamknięciu:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

Ideą tego jest otoczenie całego ciała pętli for za pomocą IIFE (Natychmiastowe wywołanie wyrażenia funkcji) i przekazanie go new_ijako parametru i przechwycenie go jako i. 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!


2
Przeczytaj raz coś podobnego w książce. Ja też wolę to, ponieważ nie musisz dotykać istniejącego kodu (tak bardzo) i staje się oczywiste, dlaczego to zrobiłeś, gdy nauczyłeś się samozwoływającego wzorca funkcji: pułapkę tej zmiennej w nowo utworzonym zakres.
DanMan

1
@DanMan Thanks. Samoczynnie wywołujące funkcje anonimowe są bardzo dobrym sposobem na radzenie sobie z brakiem zakresu zmiennej blokowej na poziomie javascript.
Kemal Dağ

3
Samo wywołanie lub samodzielne wywołanie nie jest właściwym terminem dla tej techniki, IIFE (Natychmiastowe wywołanie wyrażenia funkcji) jest dokładniejsze. Ref: benalman.com/news/2010/11/…
jherax

31

spróbuj tego krótszego

  • bez tablicy

  • bez dodatkowych pętli


for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN/


1
Twoje rozwiązanie wydaje się być poprawne, ale niepotrzebnie korzysta z funkcji, dlaczego nie tylko console.log wyników? Pierwotne pytanie dotyczy tworzenia anonimowych funkcji o takim samym zamknięciu. Problem polegał na tym, że ponieważ mają pojedyncze zamknięcie, wartość i jest taka sama dla każdego z nich. Mam nadzieję, że to rozumiesz.
Kemal Dağ

30

Oto proste rozwiązanie, które wykorzystuje forEach(działa z powrotem do IE9):

var funcs = [];
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Wydruki:

My value: 0
My value: 1
My value: 2

27

Głównym problemem z kodem pokazanym przez OP jest to, że inigdy nie jest odczytywany aż do drugiej pętli. Aby to zademonstrować, wyobraź sobie, że widzisz błąd w kodzie

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

Błąd faktycznie nie występuje, dopóki nie funcs[someIndex]zostanie wykonany (). Korzystając z tej samej logiki, powinno być oczywiste, że wartość parametru irównież nie jest gromadzona aż do tego momentu. Gdy oryginalna pętla zakończy się, i++doprowadza ido wartości, 3która powoduje i < 3awarię warunku i zakończenie pętli. W tym momencie ijest 3i kiedy funcs[someIndex]()jest używany, orazi jest oceniany, wynosi 3 - za każdym razem.

Aby temu zaradzić, musisz ocenić, ijak 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

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};

23

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ą.

var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

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ść iw 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 letzamiast tego varrozwiązuje, tworząc nowy zakres za każdym razem, gdy forpętla się uruchamia, tworząc oddzielny zakres dla każdej funkcji do zamknięcia. Różne inne techniki robią to samo z dodatkowymi funkcjami.

var funcs = []

for (let i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

( letpowoduje, że zmienne mają zakres blokowy. Bloki są oznaczone nawiasami klamrowymi, ale w przypadku pętli for zmienna inicjalizacyjna, iw naszym przypadku, jest uważana za zadeklarowaną w nawiasach klamrowych).


1
Próbowałem zrozumieć tę koncepcję, dopóki nie przeczytałem tej odpowiedzi. Dotyka bardzo ważnego punktu - wartość ijest ustalana na skalę globalną. Po forzakończeniu działania pętli globalna wartość iwynosi teraz 3. Dlatego za każdym razem, gdy ta funkcja jest wywoływana w tablicy (używając, powiedzmy, funcs[j]), funkcja iw tej funkcji odwołuje się do izmiennej globalnej (która jest 3).
Modermo,

13

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.

  • Każda definicja funkcji tworzy zakres składający się ze wszystkich zmiennych lokalnych zadeklarowanych przez vari jego arguments.
  • Jeśli mamy funkcję wewnętrzną zdefiniowaną w innej (zewnętrznej) funkcji, tworzy to łańcuch i będzie używana podczas wykonywania
  • Po uruchomieniu funkcji środowisko wykonawcze ocenia zmienne, przeszukując łańcuch zasięgu . Jeśli zmienna może zostać znaleziona w pewnym punkcie łańcucha, przestanie ją wyszukiwać i użyje jej, w przeciwnym razie będzie kontynuowana aż do osiągnięcia globalnego zasięgu, do którego należy window.

W kodzie początkowym:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

Po funcswykonaniu łańcuch zasięgu będzie function inner -> global. Ponieważ zmiennej inie można znaleźć w function inner(ani zadeklarowanej, varani przekazanej jako argumenty), kontynuuje wyszukiwanie, dopóki wartość parametru nie izostanie ostatecznie znaleziona w zasięgu globalnym, który jest window.i.

Zawinięcie go w funkcję zewnętrzną albo wyraźnie zdefiniuj funkcję pomocnika, jak harto, albo użyj funkcji anonimowej, jak Bjorn :

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

Kiedy funcszostanie wykonany, teraz będzie zasięg łańcucha function inner -> function outer. Ten czas imożna znaleźć w zakresie funkcji zewnętrznej, która jest wykonywana 3 razy w pętli for, za każdym razem ipoprawnie przypisana wartość . Nie będzie używał wartości window.ipo 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.


Rzadko piszemy ten przykładowy kod w rzeczywistości, ale myślę, że to dobry przykład na zrozumienie podstaw. Kiedy już weźmiemy pod uwagę zakres i sposób, w jaki są ze sobą powiązane, łatwiej jest zrozumieć, dlaczego działają inne „nowoczesne” sposoby, takie jak 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 iteracji forEach. Tak więc każda funkcja wewnętrzna zdefiniowana w wywołaniu zwrotnym będzie mogła użyć właściwej elwartości
wpding

13

Dzięki nowym funkcjom zakresu blokowania ES6 zarządza się:

var funcs = [];
for (let i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (let j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Kod w pytaniu OP zastępuje się letzamiast var.


constzapewnia ten sam wynik i należy go stosować, gdy wartość zmiennej się nie zmieni. Jednak użycie constwewną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życie letwewnątrz inicjatora jest poprawnie zaimplementowane w Firefoksie, więc nie musisz się tam martwić.

10

Dziwi mnie, że nikt jeszcze nie zasugerował użycia tej forEachfunkcji, aby lepiej unikać (ponownie) używania zmiennych lokalnych. W rzeczywistości nie używam for(var i ...)już z tego powodu.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

// edytowane do użycia forEachzamiast mapy.


3
.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ć.
JLRishe,

To pytanie nie dotyczy pętli nad tablicą
jherax

Cóż, chce stworzyć tablicę funkcji, ten przykład pokazuje, jak to zrobić bez angażowania zmiennej globalnej.
Christian Landgren,

9

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.

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>


8

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ą izmienną. Wszystkie wydrukowały tę samą wartość.


8

Przede wszystkim zrozum, co jest nie tak z tym kodem:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Tutaj, gdy funcs[]tablica jest inicjowana, ijest zwiększana, funcstablica jest inicjalizowana, a rozmiar functablicy staje się 3, więc i = 3,. Teraz, gdy funcs[j]()jest wywoływane, ponownie używa zmiennej i, która została już zwiększona do 3.

Teraz, aby rozwiązać ten problem, mamy wiele opcji. Poniżej dwa z nich:

  1. Możemy zainicjować iz letlub zainicjować nową zmienną indexz leti uczynić go równym i. Kiedy połączenie zostanie wykonane, indexzostanie użyte, a jego zakres zakończy się po inicjalizacji. I dla połączeń indexzostanie ponownie zainicjowany:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
  2. Inną opcją może być wprowadzenie tempFunczwracającej rzeczywistą funkcję:

    var funcs = [];
    function tempFunc(i){
        return function(){
            console.log("My value: " + i);
        };
    }
    for (var i = 0; i < 3; i++) {  
        funcs[i] = tempFunc(i);                                     
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }

8

Użyj struktury zamknięcia , zmniejszy to dodatkową pętlę for. Możesz to zrobić w jednej pętli for:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}

7

Sprawdzimy, co się właściwie stanie, gdy złożysz deklarację vari let jeden po drugim.

Przypadek 1 : za pomocąvar

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

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.

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Wniosek:

  1. Gdy deklarujesz zmienną za 'var'pomocą funkcji spoza funkcji, staje się zmienną globalną (możesz to sprawdzić, wpisując ilub window.iw oknie konsoli. Zwróci 3).
  2. Zdeklarowana funkcja nie wywoła i nie sprawdzi wartości wewnątrz funkcji, chyba że wywołasz funkcje.
  3. Po wywołaniu funkcji console.log("My value: " + i)pobiera wartość z Globalobiektu i wyświetla wynik.

PRZYPADEK 2: użycie let

Teraz wymienić 'var'z'let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

Zrób to samo, przejdź do lunety. Teraz zobaczysz dwa obiekty "Block"i "Global". Teraz rozwiń Blockobiekt, zobaczysz, że jest tam zdefiniowane „i”, a dziwne jest to, że dla każdej funkcji wartość if ijest różna (0, 1, 2).

wprowadź opis zdjęcia tutaj

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ę Blockzmienną poziomu, która jest dostępna tylko dla tej samej funkcji. Z tego powodu otrzymujemy wartość iróż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


4

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

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

Następnie możesz użyć drugiej pętli i uzyskać oczekiwany wynik lub możesz to zrobić

funcs.iterate(function(f){ f(); });

(*) 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 :)


1
Chciałbym wyjaśnić głosowanie negatywne. Kod rozwiązuje bieżący problem. Warto byłoby wiedzieć, jak potencjalnie poprawić kod
Rune FS,

1
Co to jest 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.
jherax

1
@jherax są to lub oczywiście oczywiste ulepszenia. Dziękuję za komentarz. Mógłbym przysiąc, że istnieje już link. Bez tego post był chyba bezcelowy :). Mój początkowy pomysł, by go nie udostępniać, polegał na tym, że nie próbowałem naciskać na wykorzystanie własnej biblioteki, ale raczej deklaratywny pomysł. Jednak w perspektywie w pełni zgadzam się, że link powinien tam być
Rune FS

4

Wolę używać forEachfunkcji, która ma swoje własne zamknięcie z tworzeniem pseudo zakresu:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}

To wygląda brzydiej niż zakresy w innych językach, ale IMHO jest mniej monstrualne niż inne rozwiązania.


Wolisz to od czego? To wydaje się być komentarzem w odpowiedzi na inną odpowiedź. W ogóle nie odnosi się do rzeczywistego pytania (ponieważ nie przypisujesz funkcji, która ma zostać wywołana później, gdziekolwiek).
Quentin,

Jest to
ściśle

Teraz nie wydaje się znacząco różnić od przyjętej odpowiedzi.
Quentin,

Nie. W przyjętej odpowiedzi sugeruje się użycie „jakiejś tablicy”, ale w odpowiedzi mamy do czynienia z zakresem, są to absolutnie różne rzeczy, które niestety nie mają dobrego rozwiązania w js, więc moja odpowiedź próbuje rozwiązać problem w dobry i praktyczny sposób
Rax Wunter,

@Quentin Poleciłbym zbadać rozwiązanie przed zminimalizowaniem
Rax Wunter

4

I jeszcze jedno rozwiązanie: zamiast tworzyć kolejną pętlę, po prostu powiąż thisfunkcję return.

var funcs = [];

function createFunc(i) {
  return function() {
    console.log('My value: ' + i); //log value of i.
  }.call(this);
}

for (var i = 1; i <= 5; i++) {  //5 functions
  funcs[i] = createFunc(i);     // call createFunc() i=5 times
}

Wiążąc to , rozwiązuje również problem.


3

Wiele rozwiązań wydaje się poprawnych, ale nie wspominają o nazwie, Curryingktóra jest funkcjonalnym wzorcem projektowym programowania w sytuacjach takich jak tutaj. 3-10 razy szybszy niż bindowanie w zależności od przeglądarki.

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}

Zobacz wzrost wydajności w różnych przeglądarkach .


@TinyGiant Przykład z zwracaną funkcją wciąż jest zoptymalizowany pod kątem wydajności. Nie wskoczyłbym na modę funkcji strzałek, jak wszyscy blogerzy JavaScript. Wyglądają fajnie i czysto, ale promują wbudowane funkcje pisania zamiast korzystania ze wstępnie zdefiniowanych funkcji. Może to być nieoczywista pułapka w gorących miejscach. Innym problemem jest to, że nie są one tylko cukrem syntaktycznym, ponieważ wykonują niepotrzebne wiązania, tworząc w ten sposób zamknięcia do owijania.
Paweł

2
Ostrzeżenie dla przyszłych czytelników: w tej odpowiedzi niedokładnie stosuje się termin Curry . „Curry polega na rozbiciu funkcji, która bierze wiele argumentów na szereg funkcji, które biorą udział w argumentach.” . Ten kod nic takiego nie robi. Wszystko, co tutaj zrobiłeś, to pobrać kod z zaakceptowanej odpowiedzi, przenieść niektóre rzeczy, zmienić styl i nazwać trochę, a następnie nazwać to curry, czego kategorycznie nie ma.

3

Twój kod nie działa, ponieważ działa:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

Pytanie brzmi: jaka jest wartość zmiennej iprzy wywołaniu funkcji? Ponieważ pierwsza pętla jest tworzona z warunkiem i < 3, zatrzymuje się natychmiast, gdy warunek jest fałszywy, więc tak jest i = 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 ifunkcji, a dopiero potem zapisanie funkcji w funcs. Można to zrobić na przykład w następujący sposób:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

W ten sposób każda funkcja będzie miała swoją zmienną xi ustawiamy xją na wartość iw każdej iteracji.

To tylko jeden z wielu sposobów rozwiązania tego problemu.


3
var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function(param) {          // and store them in funcs
    console.log("My value: " + param); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j](j);                      // and now let's run each one to see with j
}

3

Użyj let (block-scope) zamiast var.

var funcs = [];
for (let i = 0; i < 3; i++) {      
  funcs[i] = function() {          
    console.log("My value: " + i); 
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      
}

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.