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

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
}

Wypisuje to:

Moja wartość: 3
Moja wartość: 3
Moja wartość: 3

Natomiast chciałbym, żeby wyszł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");
for (var i = 0; i < buttons.length; i++) {          // let's create 3 functions
  buttons[i].addEventListener("click", function() { // as event listeners
    console.log("My value: " + i);                  // each should log its value.
  });
}
<button>0</button><br>
<button>1</button><br>
<button>2</button>

... lub kod asynchroniczny, np. za pomocą Obietnice:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for(var i = 0; i < 3; i++){
  wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}

Jakie jest rozwiązanie tego podstawowego problemu?

Author: Xufox, 2009-04-15

30 answers

Cóż, problem polega na tym, że zmienna i, wewnątrz 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 każdej funkcji do oddzielnej, niezmiennej 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++) {
    funcs[j]();                        // and now let's run each one to see
}

Ponieważ nie ma zakresu blokowego w JavaScript-tylko zakres funkcji - owijając tworzenie funkcji w nową funkcję, zapewniasz że wartość " ja " pozostaje taka, jak zamierzałeś.


2015 rozwiązanie: forEach

Przy stosunkowo szerokiej dostępności funkcji Array.prototype.forEach (w 2015 roku), warto zauważyć, że w sytuacjach, w których iteracja dotyczy głównie tablicy wartości, .forEach() zapewnia czysty, naturalny sposób uzyskania odrębnego zamknięcia dla każdej iteracji. To znaczy, zakładając, że masz jakąś tablicę zawierającą wartości (referencje DOM, obiekty, cokolwiek) i pojawia się problem z ustawieniem w górę wywołania specyficzne dla każdego elementu, można 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żytej w pętli .forEach będzie jej własnym zamknięciem. Parametr przekazany do tej funkcji obsługi jest elementem tablicy specyficznym dla danego etapu iteracji. Jeśli jest używany w asynchronicznym wywołaniu zwrotnym, nie koliduje z żadnym innym wywołaniem zwrotnym ustanowionym na innych etapach iteracji.

Jeśli przypadkiem pracujesz w jQuery, $.each() funkcja daje podobną możliwość.


Rozwiązanie ES6: let

ECMAScript 6 (ES6), najnowsza wersja JavaScript, zaczyna być obecnie implementowana w wielu wiecznie zielonych przeglądarkach i systemach backendowych. Istnieją również transpilery, takie jak Babel, które konwertują ES6 na ES5, aby umożliwić korzystanie z nowych funkcji na starszych systemach.

ES6 wprowadza nowe słowa kluczowe let i const, które mają inny zakres niż zmienne oparte na var. Na przykład w pętli z indeksem opartym na let, każda iteracja w pętli będzie miała nową wartość i, gdzie każda wartość jest ustawiona wewnątrz pętli, więc Twój kod będzie działał zgodnie z oczekiwaniami. Istnieje wiele zasobów, ale polecam 2ality ' s block-scoping post jako świetne źródło informacji.

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

Uważaj jednak na to, że IE9-IE11 i Edge przed Edge 14 obsługują let, ale powyższe źle się rozumie (nie tworzą za każdym razem nowego i, więc wszystkie powyższe funkcje będą logować 3 tak, jak would if we used var). Edge 14 w końcu się zgadza.

 1844
Author: harto,
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
2018-06-06 10:19:14

Spróbuj:

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

Edytuj (2014):

Osobiście uważam, że @ Aust ' s bardziej aktualna odpowiedź na temat używania .bind to najlepszy sposób na takie rzeczy. Istnieje również lo-dash/underscore _.partial, gdy nie potrzebujesz lub nie chcesz zadzierać z bind'S thisArg.

 346
Author: Bjorn Tipling,
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-05-23 12:18:27

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

UPDATE

Jak zauważyli @squint i @ mekdev, uzyskujesz lepszą wydajność, 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]();
}
 316
Author: Aust,
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
2018-02-21 16:03:26

Użycie natychmiast wywołanego wyrażenia Funkcji, najprostszy i najbardziej czytelny sposób załączenia zmiennej indeksowej:

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);

}

Wysyła iterator i do funkcji anonimowej, którą definiujemy jako index. Tworzy to zamknięcie, w którym zmienna i zostaje zapisana do późniejszego użycia w dowolnej funkcji asynchronicznej w ramach IIFE.

 238
Author: neurosnap,
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-09-22 13:47:05

Trochę późno na imprezę, ale badałem ten problem dzisiaj i zauważyłem, że wiele odpowiedzi nie do końca odnosi się do tego, jak Javascript traktuje zakresy, co zasadniczo sprowadza się do tego.

Tak jak wielu innych wspomniało, problem polega na tym, że wewnętrzna funkcja odwołuje się do tej samej zmiennej i. Więc dlaczego po prostu nie utworzymy nowej zmiennej lokalnej każdej iteracji i zamiast tego będziemy mieli wewnętrzne odniesienie do funkcji?

//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 wcześniej, gdy każda wewnętrzna funkcja wyprowadzała ostatnią wartość przypisaną do i, Teraz każda wewnętrzna funkcja wyprowadza ostatnią wartość przypisaną do ilocal. Ale czy każda iteracja nie powinna mieć własnego ilocal?

Okazuje się, że w tym problem. Każda iteracja ma ten sam zakres, więc każda iteracja po pierwszej nadpisuje ilocal. Z MDN :

Ważne: JavaScript nie ma zakresu bloków. Zmienne wprowadzone blokiem mają zasięg do zawierające funkcję lub skrypt, a efekty ich ustawienia utrzymują się poza samym blokiem. Innymi słowy, polecenia blokowe nie wprowadzają zakresu. Chociaż bloki" standalone " są poprawną składnią, nie chcesz używać bloków autonomicznych w JavaScript, ponieważ nie robią tego, co myślisz, że robią, jeśli myślisz, że robią coś takiego w C lub Javie.

Dla podkreślenia:

JavaScript nie ma zakresu bloków. Zmienne wprowadzone z blok jest ograniczony do funkcji zawierającej lub skryptu

Możemy to zobaczyć, sprawdzając ilocal, zanim 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 deklarujesz zmienną, Javascript nie wyświetli błędu, a JSLint nawet nie wyświetli Ostrzeżenia. Dlatego też najlepszym sposobem na rozwiązanie tego problemu jest skorzystanie z zamknięć, co jest zasadniczo ideą, że w Javascript funkcje wewnętrzne mają dostęp do zmiennych zewnętrznych, ponieważ zakres wewnętrzny "zamyka" zakres zewnętrzny.

Zamknięcia

Oznacza to również, że wewnętrzne funkcje "trzymają" zewnętrzne zmienne i utrzymują je przy życiu, nawet jeśli zewnętrzna funkcja powróci. Aby to wykorzystać, tworzymy i wywołujemy funkcję wrappera wyłącznie w celu utworzenia nowego zakresu, deklarujemy ilocal w nowym zakresie i zwracamy wewnętrzną funkcję, która używa 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]();
}

Tworzenie funkcji wewnętrznej wewnątrz funkcja wrapper daje funkcji wewnętrznej prywatne środowisko, do którego tylko ona może uzyskać dostęp, "zamknięcie". Tak więc za każdym razem, gdy wywołujemy funkcję wrappera, tworzymy nową wewnętrzną funkcję z jej własnym oddzielnym środowiskiem, zapewniając, że zmienne ilocal nie kolidują ze sobą i nie nadpisują się nawzajem. Kilka drobnych optymalizacji daje ostateczną odpowiedź, że wielu innych użytkowników tak dał:

//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);
    };
}

Update

Z ES6 teraz mainstream, możemy teraz używać nowego let słowo kluczowe do utworzenia 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 proste! Aby uzyskać więcej informacji zobacz ta odpowiedź , na której opierają się moje informacje.
 133
Author: woojoo666,
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
2018-03-01 22:43:11

Ponieważ ES6 jest obecnie szeroko wspierany, najlepsza odpowiedź na to pytanie uległa zmianie. ES6 dostarcza słowa kluczowe let i const dla tej dokładnej okoliczności. Zamiast bawić się zamknięciami, możemy po prostu użyć let, aby ustawić zmienną zakresu pętli w taki sposób:

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

val Następnie wskaże obiekt, który jest specyficzny dla danego obrotu pętli i zwróci poprawną wartość bez dodatkowej notacji zamknięcia. To oczywiście znacznie upraszcza to problem.

const jest podobna do let z dodatkowym ograniczeniem, że nazwa zmiennej nie może być przekierowana do nowego odniesienia po początkowym przypisaniu.

Wsparcie dla przeglądarek jest teraz tutaj dla tych, którzy kierują się najnowszymi wersjami przeglądarek. const/let są obecnie obsługiwane w najnowszych przeglądarkach Firefox, Safari, Edge i Chrome. Jest również obsługiwany w Node i możesz 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 na to, że IE9-IE11 i Edge przed Edge 14 obsługują let, ale powyższe błędy są błędne (nie tworzą za każdym razem Nowego i, więc wszystkie powyższe funkcje będą logować 3 tak, jak gdybyśmy użyli var). Edge 14 w końcu się zgadza.

 124
Author: Ben McCormick,
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
2018-04-11 17:05:30

Innym sposobem jest powiedzenie, że i w twojej funkcji jest związana w czasie wykonywania funkcji, a nie w czasie tworzenia funkcji.

Kiedy tworzysz zamknięcie, i jest odniesieniem do zmiennej zdefiniowanej w zewnętrznym zakresie, a nie jej kopią, jak to było podczas tworzenia zamknięcia. Zostanie on oceniony w momencie realizacji.

Większość innych odpowiedzi zapewnia sposoby obejścia, tworząc kolejną zmienną, która nie zmieni wartości dla ty.

Pomyślałem, że dodam Wyjaśnienie dla jasności. Jeśli chodzi o rozwiązanie, osobiście wybrałbym Harto, ponieważ jest to najbardziej zrozumiały sposób na zrobienie tego z odpowiedzi tutaj. Każdy z opublikowanych kodu będzie działać, ale chciałbym zdecydować się na fabrykę zamknięcia zamiast konieczności pisania sterty komentarzy, aby wyjaśnić, dlaczego deklaruję nową zmienną (Freddy i 1800) lub mają dziwne wbudowane składnię zamknięcia (apphacker).
 78
Author: Darren Clark,
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-12-26 11:07:51

Musisz zrozumieć, że zakres zmiennych w javascript opiera się na funkcji. Jest to istotna różnica niż np. w c#, gdzie masz zakres blokowy, a kopiowanie zmiennej do tej wewnątrz for będzie działać.

Owinięcie go w funkcję, która ocenia zwracanie funkcji, jak odpowiedź apphackera, załatwi sprawę, ponieważ zmienna ma teraz zakres funkcji.

Istnieje również słowo kluczowe let zamiast var, które pozwala na użycie zakresu bloku zasada. W takim przypadku zdefiniowanie zmiennej wewnątrz for załatwiłoby 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]();                        
}
 61
Author: eglasius,
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
2009-04-15 06:31:15

Oto kolejna odmiana tej techniki, podobna do Bjorna (apphacker), która pozwala przypisać wartość zmiennej wewnątrz funkcji, a nie przekazywać ją jako parametr, co może być jaśniejsze czasami:

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 używanej techniki, zmienna {[1] } staje się pewnego rodzaju zmienną statyczną, związaną ze zwracaną kopią funkcji wewnętrznej. Czyli zmiany jego wartości są zachowywane między wywołaniami. To może być bardzo przydatne.

 49
Author: Boann,
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
2012-08-07 08:45:35

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

Funkcja definiuje nowe środowisko

Rozważmy:

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

Po każdym wywołaniu makeCounter, {counter: 0} powoduje utworzenie nowego obiektu. Również nowa kopia obj jest również tworzony w celu odniesienia się do nowego obiektu. Tak więc counter1 i counter2 są niezależne od siebie.

Zamknięcia w pętlach

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

Rozważ:

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] Nie niezależne. W rzeczywistości działają na tym samym obj!

Dzieje się tak dlatego, że istnieje tylko jedna kopia obj dzielona przez wszystkie iteracje pętli, być może ze względu na wydajność. Mimo że {counter: 0} tworzy nowy obiekt w każdej iteracji, ta sama Kopia obj zostanie zaktualizowana o odniesienie do najnowszego obiektu.

Rozwiązaniem jest użycie innej funkcji pomocniczej:

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

To działa, ponieważ zmienne lokalne bezpośrednio w zakresie funkcji, jak również zmienne argumentu funkcji, są przydzielane nowe egzemplarze po wejściu.

Aby uzyskać szczegółową dyskusję, zobacz pułapki zamknięcia JavaScript i użycie

 45
Author: Lucas,
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
2013-04-20 09:59:57

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 alarmuje "2", za 3 razy. Dzieje się tak dlatego, że funkcje anonimowe utworzone w pętli for dzielą to samo zamknięcie i w tym zamknięciu wartość i jest taka sama. Użyj tego, aby zapobiec współdzielonemu 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ą stojącą za tym jest zamknięcie całego ciała pętli for za pomocą IIFE (natychmiast wywołane wyrażenie funkcji) i przekazanie new_i jako parametru i przechwycenie go jako i. Ponieważ funkcja anonimowa jest wykonywana natychmiast, wartość i jest inna dla każdej funkcji zdefiniowanej wewnątrz 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 z założenia, nie powinno to być problemem w ogóle!

 41
Author: Kemal Dağ,
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-12-28 08:07:40

Spróbuj tego krótszego

  • No array

  • No extra for loop


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

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

Http://jsfiddle.net/7P6EN/

 24
Author: yilmazburk,
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
2013-09-19 14:20:24

Główny problem z kodem pokazanym przez OP polega na tym, że i nie jest czytany do drugiej pętli. Aby zademonstrować, wyobraź sobie, że widzisz błąd wewnątrz kodu

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

Błąd nie występuje, dopóki funcs[someIndex] nie zostanie wykonany (). Używając tej samej logiki, powinno być oczywiste, że wartość i również nie jest pobierana do tego punktu. Po zakończeniu oryginalnej pętli, i++ przenosi i do wartości 3, co skutkuje niepowodzeniem warunku i < 3 i pętlą koniec. W tym momencie i jest 3, a więc kiedy funcs[someIndex]() jest używane, a i jest oceniane, to jest 3-za każdym razem.

Aby to ominąć, musisz ocenić i Jak to się spotyka. Zauważ, że stało się to już w formie funcs[i] (gdzie są 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 skonstruowanie obiektu funkcji, który będzie być w stanie zamknąć nad zmienną. To można osiągnąć w ten sposób

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
 20
Author: Travis J,
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
2014-03-05 23:03:24

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
}

Druki:

My value: 0
My value: 1
My value: 2
 19
Author: Daryl,
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
2014-05-03 03:42:57

Funkcje JavaScript "zamykają" zakres, do którego mają dostęp podczas deklaracji, i zachowują dostęp do tego zakresu, nawet gdy zmienne w tym zakresie ulegają zmianie.

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 zakres globalny (globalny, po prostu dlatego, że jest to zakres, w którym są zadeklarowane).

Później te funkcje są wywoływane rejestrując najbardziej aktualną wartość i w globalnym zasięgu. To magia i frustracja zamknięcie.

"funkcje JavaScript zamykają zakres, w którym są zadeklarowane, i zachowują dostęp do tego zakresu nawet wtedy, gdy zmieniają się wartości zmiennych wewnątrz tego zakresu."

Użycie let zamiast var rozwiązuje ten problem, tworząc nowy zakres za każdym razem, gdy pętla for działa, tworząc oddzielony 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]()
}

(let tworzy zmienne o zasięgu blokowym zamiast zakresu funkcji. 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.)

 16
Author: Costa,
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-02-15 04:09:52

Po zapoznaniu się z różnymi rozwiązaniami, chciałbym dodać, że powodem, dla którego te rozwiązania działają, jest oparcie się na koncepcji scope chain . W ten sposób JavaScript rozwiązuje zmienną podczas wykonywania.

  • każda definicja funkcji tworzy zakres składający się ze wszystkich lokalnych zmienne deklarowane przez var i jej arguments.
  • jeśli mamy wewnętrzną funkcję zdefiniowaną wewnątrz innej (zewnętrznej) funkcji, to tworzy łańcuch i będzie używany podczas wykonywania
  • gdy funkcja jest wykonywana, runtime ocenia zmienne poprzez przeszukiwanie łańcucha zakresu. Jeśli zmienna zostanie znaleziona w pewnym punkcie łańcucha, przestanie ona przeszukiwać i używać jej, w przeciwnym razie będzie kontynuowana aż do osiągnięcia globalnego zakresu, który należy do 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 wykonaniu funcs łańcuch zakresu będzie function inner -> global. Ponieważ zmienna i nie może zostać znaleziona w function inner (ani zadeklarowana za pomocą var, ani przekazana jako argumenty), kontynuuje wyszukiwanie, dopóki wartość i nie zostanie ostatecznie znaleziona w globalnym zakresie, który wynosi window.i.

Przez owinięcie go w zewnętrzną funkcję albo jawnie zdefiniuj funkcję pomocniczą, taką jak harto did lub użyj anonimowej funkcji, takiej jak Bjorn did:

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 funcs zostanie wykonane, teraz łańcuch zakresu będzie function inner -> function outer. Czas ten i można znaleźć w zewnętrznym zakresie funkcji, który jest wykonywany 3 razy w pętli for, za każdym razem ma wartość i związana poprawnie. Nie użyje wartości window.i, gdy wewnętrzne zostanie wykonane.

Więcej szczegółów można znaleźć tutaj
Obejmuje to powszechny błąd w tworzeniu zamknięcia w pętli, jak to, co mamy tutaj, a także dlaczego potrzebujemy zamknięcia i rozważania wydajności.

 11
Author: wpding,
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-05-23 12:02:57

Z nowymi funkcjami ES6 block level scoping jest zarządzany:

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ę let zamiast var.

 10
Author: Prithvi Uppalapati,
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-12-26 20:27:59

Jestem zaskoczony, że nikt jeszcze nie zasugerował użycia funkcji forEach, aby lepiej unikać (re)używania zmiennych lokalnych. W rzeczywistości nie używam już for(var i ...) z tego powodu.

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

// edited to use forEach zamiast map.

 6
Author: Christian Landgren,
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-12-26 19:51:50

Po pierwsze, 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 tablica funcs[] jest inicjalizowana, i jest zwiększana, Tablica funcs jest inicjalizowana i rozmiar tablicy func staje się 3, więc i = 3,. Teraz po wywołaniu funcs[j](), ponownie używa zmiennej i, która została już zwiększona do 3.

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

  1. Możemy zainicjować i za pomocą let lub zainicjalizuj nową zmienną index za pomocą let i ustaw ją równą i. Kiedy wywołanie jest wykonywane, index zostanie użyte, a jego zakres zakończy się po inicjalizacji. A dla wywołania, index zostanie zainicjowane ponownie:

    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 tempFunc, która zwraca 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]();                        
    }
    
 5
Author: Ali Kahoot,
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-12-26 22:46:45

To pytanie naprawdę pokazuje historię JavaScript! Teraz możemy uniknąć blokowania zakresów za pomocą funkcji strzałek i obsługiwać pętle bezpośrednio z węzłów DOM przy użyciu metod obiektowych.

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>
 4
Author: sidhuko,
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
2018-01-13 13:17:57

Sprawdzimy, co tak naprawdę się dzieje, gdy zadeklarujesz var i let jeden po drugim.

Case1 : using 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 swoje okno konsoli chrome naciskając F12 i odśwież stronę. Wydaj co 3 Funkcje wewnątrz tablicy.Zobaczysz nieruchomość o nazwie [[Scopes]].Rozwiń to. Zobaczysz jedną obiekt array o nazwie "Global", rozwiń ten. Znajdziesz nieruchomość 'i' zadeklarowaną w obiekt, który ma wartość 3.

Tutaj wpisz opis obrazka

Tutaj wpisz opis obrazka

Wniosek:

  1. kiedy deklarujesz zmienną za pomocą 'var' poza funkcją, staje się ona zmienną globalną (możesz sprawdzić wpisując i lub window.i w konsoli window.It powróci 3).
  2. zadeklarowana funkcja annominous nie wywoła i nie sprawdzi wartości wewnątrz funkcji, chyba że wywołasz funkcje.
  3. gdy wywołujesz funkcję , console.log("My value: " + i) pobiera wartość ze swojego obiektu Global i wyświetla wynik.

CASE2: using let

Teraz zastąp 'var' '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, idź do lunety . Teraz zobaczysz dwa obiekty "Block" i "Global". Teraz rozwiń Block obiekt, ty będzie tam zdefiniowane 'i', A dziwne jest to , że dla każdej funkcji wartość if i jest inna (0 , 1, 2).

Tutaj wpisz opis obrazka

Wniosek:

Kiedy zadeklarujesz zmienną za pomocą 'let' nawet poza funkcją, ale wewnątrz pętli, ta zmienna nie będzie globalną zmienna, stanie się zmienną poziomu Block, która jest dostępna tylko dla tej samej funkcji.To jest powód, my otrzymujemy wartość i różną dla każdej funkcji, gdy wywołujemy funkcje.

Aby uzyskać więcej informacji o tym , jak działa closer, przejdź do Niesamowite Video tutorial https://youtu.be/71AtaJpJHw0

 4
Author: Bimal Das,
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
2018-01-16 14:29:57

Oryginalny przykład nie zadziałał, ponieważ wszystkie zamknięcia utworzone w pętli odnosiły się do tej samej klatki. W efekcie, posiadanie 3 metod na jednym obiekcie z tylko jedną zmienną i. Wszystkie wydrukowały tę samą wartość.

 3
Author: jottos,
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-12-26 20:27:18

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

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
 3
Author: Vikash Singh,
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-12-27 00:06:45

Wolę używać funkcji forEach, 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ż w innych językach, Ale IMHO mniej potwornie niż w innych rozwiązaniach.

 2
Author: Rax Wunter,
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-12-17 15:14:47

Możesz użyć modułu deklaratywnego dla list danych, takich jak query-js (*). W takich sytuacjach osobiście uważam podejście deklaratywne za mniej zaskakujące

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

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

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

(*) Jestem autorem query-js i do tego stronniczy w stosunku do korzystania z niego, więc nie bierz moich słów jako zalecenia dla wspomnianej biblioteki tylko dla deklaratywnego podejścia:)

 1
Author: Rune FS,
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-10-27 10:15:11

I jeszcze jedno rozwiązanie: zamiast tworzyć kolejną pętlę, po prostu połącz {[1] } z funkcją 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.
 1
Author: pixel 67,
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-08-28 16:37:06

Wiele rozwiązań wydaje się poprawnych, ale nie wspominają, że nazywa sięCurrying który jest funkcjonalnym wzorcem programowania dla takich sytuacji jak tutaj. 3-10 razy szybciej niż bind 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 .

 1
Author: Pawel,
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-12-26 21:21:57

Twój kod nie działa, bo to co robi to:

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?

Teraz pytanie, Jaka jest wartość zmiennej i Gdy funkcja jest wywoływana? Ponieważ pierwsza pętla jest tworzona z warunkiem i < 3, zatrzymuje się natychmiast, gdy warunek jest fałszywy, więc jest to i = 3.

Musisz zrozumieć, że w czasie tworzenia funkcji żaden z ich kodu nie jest wykonywany, jest on zapisywany tylko na później. I tak, gdy zostaną wywołane później, interpreter wykonuje oni i pyta: "Jaka jest aktualna wartość i?"

Więc twoim celem jest najpierw zapisać wartość {[2] } do funkcji, a dopiero potem zapisać funkcję do funcs. Można to zrobić na przykład w ten 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 własną zmienną x i ustawiamy tę x na wartość i w każdej iteracji.

To tylko jeden z wielu sposobów rozwiązania tego problemu.
 1
Author: Buksy,
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-12-26 22:53:29
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
}
 1
Author: ashish yadav,
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
2018-07-13 08:02:09

COUNTER BEING A PRIMITIVE

Zdefiniujmy funkcje wywołania zwrotnego w następujący sposób:

// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2

Po zakończeniu limitu czasu wyświetli 2 dla obu. Dzieje się tak dlatego, że funkcja zwrotna uzyskuje dostęp do wartości na podstawie zakresu leksykalnego , gdzie została zdefiniowana funkcja.

Aby przekazać i zachować wartość podczas definiowania wywołania zwrotnego, możemy utworzyć zamknięcie , aby zachować wartość przed wywołaniem wywołania zwrotnego. Można to zrobić jako follows:

function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2

Teraz to, co jest w tym szczególne, to " prymitywy są przekazywane przez wartość i kopiowane. Tak więc po zdefiniowaniu zamknięcia zachowują one wartość z poprzedniej pętli."

LICZNIK BĘDĄCY OBIEKTEM

Ponieważ zamknięcia mają dostęp do macierzystych zmiennych funkcyjnych poprzez odniesienie, podejście to różniłoby się od podejścia pierwotnego.

// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
    var index = { i: 0 };
    for (index.i=0; index.i<2; index.i++) {
        setTimeout(function() {
            console.log('test3: ' + index.i);
        });
    }
}
test3();
// 2
// 2

Tak więc, nawet jeśli zostanie utworzone zamknięcie dla zmiennej przekazywanej jako obiekt, wartość indeksu pętli nie będzie być zachowane. Ma to na celu pokazanie, że wartości obiektu nie są kopiowane, podczas gdy są dostępne przez odniesienie.

function test4() {
    var index = { i: 0 };
    function sendRequest(index, i) {
        setTimeout(function() {
            console.log('index: ' + index);
            console.log('i: ' + i);
            console.log(index[i]);
        });
    }

    for (index.i=0; index.i<2; index.i++) {
        sendRequest(index, index.i);
    }
}
test4();
// index: { i: 2}
// 0
// undefined

// index: { i: 2}
// 1
// undefined
 0
Author: jsbisht,
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-12-27 00:01:42