2017-01-18 7 views
7

Jestem absolutną nowicjuszką i po prostu przeczytałem to w: JavaScript: The Good Parts.Dlaczego nie zachęca się do tworzenia funkcji w pętli w JavaScript?

W rozdziale dotyczącym zakresu podano: "Ważne jest, aby zrozumieć, że funkcja wewnętrzna ma dostęp do rzeczywistych zmiennych funkcji zewnętrznych, a nie do kopii, aby uniknąć następującego problemu:." A następnie dwa poniższe przykłady wyglądać następująco:

// zły przykład

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (e) { 
      alert(i); 
     }; 
    } 
}; 

// END ZŁY PRZYKŁAD

var add_the_handlers = function (nodes) { 
    var helper = function (i) { 
     return function (e) { 
      alert(i); 
     }; 
    }; 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     modes[i].onclick = helper(i); 
    } 
}; 

Według autora Drugim przykładem jest lepsza, ponieważ nie robi” t używaj pętli wewnątrz funkcji, w przeciwnym razie może być marnotrawstwem obliczeniowym. Ale jestem zagubiony i nie wiem, co z nimi zrobić. Jak umieścić swoją teorię w rzeczywistym zastosowaniu? Czy ktoś ilustrujący te dwa przykłady może łączyć HTML?

+2

Ważne, aby zrozumieć, że gdy zdarzenie wystąpi ... "i" nie będzie tym, co chcesz. Zobacz http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical -przyp. – charlietfl

+0

Co rozumiesz przez "dwa przykłady łączą HTML"? –

Odpowiedz

0

Zły przykład tworzy wiele programów obsługi zdarzeń; Jeden na wydarzenie. Dobrym przykładem jest utworzenie pojedynczej procedury obsługi zdarzenia i przypisanie jej do wszystkich zdarzeń.

Z tym przykrym przykładem utworzyłeś wiele oddzielnych funkcji zamiast jednego. Może to być dużo dodatkowych kosztów i potencjalnych problemów z zasięgiem. Należą do nich problemy z zamknięciem, takie jak zdarzenie tylko zwalniające dla ostatniego elementu w pętli.

Dodatkowo, dobry przykład umożliwia łatwiejsze anulowanie subskrypcji zdarzeń, ponieważ masz dostęp do pierwotnego wskaźnika funkcji.

Dobry przykład jest po prostu łatwiejszy do odczytania i zrozumienia. Pętla służy tylko do tworzenia elementów i wiązania ich zdarzeń; Obsługa tych wydarzeń odbywa się gdzie indziej.

+2

Zwykle napotykasz również na pytanie "dlaczego moje zdarzenie wyzwala tylko ostatnią wartość" i "problemów z zamknięciem. –

+0

Dzięki, uwzględniłem to w odpowiedzi. – Soviut

+2

Nie sądzę. Dobry przykład tworzy oddzielną, anonimową funkcję dla każdego elementu węzła, wewnątrz 'helper', aby uchwycić' i' w zamknięciu, i przywraca zwracaną funkcję jako wartość atrybutu 'onclick'. 'helper' nie może być użyty do usunięcia detektora zdarzeń dodanego przez' addEventListener'. – traktor53

6

Problem polega na zamknięciu. Wewnętrzne funkcje mają dostęp do zmiennej i zdefiniowanej poza tymi funkcjami. Po wykonaniu wszystkich iteracji pętli, zmienna i będzie miała wartość nodes.length. Po kliknięciu na nodes[0], alert powie: nodes.length, co nie jest tym, czego można się spodziewać. (Można się spodziewać, że alert powie 0.) To samo dotyczy, gdy klikniesz na nodes[1], nodes[2], itp. Alarm dla wszystkich z nich powie nodes.length.

+0

W rzeczywistości wartość i pokazana w alarmie będzie miała wartość "nodes.length", a nie 'nodes.length - 1', ponieważ końcowa wartość zmiennej pętli jest pierwszą wartością, dla której warunek się nie powiedzie, a nie ostatnią wartością dla których warunek przechodzi. Właśnie o tym mówił Crockford - jeśli miałbyś uruchomić "zły kod" i miałeś elementy od 0 do 4, wszystkie alerty miałyby "5", ponieważ wszystkie one mają tę samą zmienną, która jest zawarta w pięciu różne zamknięcia odnoszące się do tego samego zakresu. – PMV

+0

Tak, masz rację. Off o 1 problem w mojej odpowiedzi. –

0

Jak wspomina Soviut, tworzysz wiele programów obsługi zdarzeń w złym przykładzie. Co więcej, ważne jest, aby zwrócić uwagę, że złe przykładowe funkcje odnoszą się do tej samej zmiennej i, co oznacza, że ​​wszystkie z nich będą miały ostatnią wartość nodes.length podczas ich wykonywania.

Dzieje się tak dlatego, że tworzone jest zamknięcie. Więcej informacji na ten temat można uzyskać pod adresem Closures .

+0

Oba przykłady tworzą oddzielną funkcję dla każdej procedury obsługi zdarzeń. Dobry przykład nie jest bardziej skuteczny niż zły; jednak poprawnie przechwytuje * bieżącą * wartość zmiennej "i" wewnątrz każdego zamknięcia. –

0

Po pierwsze, w złym przykładzie funkcja jest tworzona dla każdej procedury obsługi zdarzenia; pętla tworzy wiele obiektów funkcji. Podczas gdy w drugim przykładzie tworzona jest jedna funkcja i odwołuje się ją z wnętrza pętli. Dzięki temu oszczędzasz dużo pamięci.

Po drugie, w złym przykładzie, gdy działa wartość "i", funkcja nie zachowuje wartości, a po uruchomieniu zawsze zwraca ostatnią wartość "i".W dobrym przykładzie jednak, gdy "i" jest przekazywane do funkcji, ta wartość jest zachowywana jako środowisko leksykalne funkcji, a gdy zostanie wywołana, zwróci poprawną wartość.

Po trzecie, jak wspomniano przez @Gary'ego Hayesa, możemy chcieć użyć tej funkcji również gdzie indziej. Najlepiej więc zachować niezależność od pętli.

+1

Dobra odpowiedź. Możesz również uruchomić tę funkcję w innym miejscu, bez konieczności uruchamiania pętli. –

+0

Tak, bardzo prawdziwe @GaryHayes ... Proszę, edytuj moją odpowiedź i dodaj ten doskonały punkt! – poushy

+0

Ten sam problem co poprzednia odpowiedź: tyle funkcji kliknięć jest tworzonych, ponieważ istnieją węzły zarówno w dobrych, jak iw złych przykładach. – traktor53

1

Możesz to sprawdzić, działając w HTML tutaj: https://jsfiddle.net/vdrr4519/.

Elementy "multifunc" mają przykład z wieloma funkcjami, "singlefunc" - z jednym. Widzisz, bierzemy wszystkie elementy z klasą i przekazujemy je do funkcji.

multifunc(document.querySelectorAll('.multifunc')); 

Funkcja działa z pętlą "dla" i dodaje detektor zdarzeń "kliknięcie". Tak więc element powinien ostrzec swój indeks kliknięciem (począwszy od 0). Ale w przypadku wielu funkcji powstaje błędna wartość (z powodu zamknięcia inne odpowiedzi również wskazują na problem).

Myślę, że powinienem również powiedzieć, że nie jest to kwestia funkcji pojedynczych funkcji/różnych funkcji - to kwestia pracy z zamknięciami. Widzisz, mogę wdrożyć przykład z wieloma zamknięciami: https://jsfiddle.net/pr7gqtdr/1/. Ja w zasadzie to samo, co robisz w przewodnika jednofunkcyjne, ale za każdym razem wywołać nową „pomocnika” funkcję:

nodes[i].onclick = function (i) { 
    return function (e) { 
     alert(i); 
    }; 
}(i); 

See, to (i) na końcu jest natychmiastowe wywołanie funkcji, więc onclick dostaje funkcja z ustawioną zmienną i w zamknięciu.

Ale opcje pojedynczej funkcji są nieco lepsze, ponieważ są bardziej wydajne pod względem pamięci, jak sądzę. Funkcje to obiekty. Jeśli utworzysz wiele z nich, ogólnie zabierasz więcej pamięci. Wybierając z nich, trzymałbym się opcji funkcji "obsługi".