Pytanie Dla każdego nad tablicą w JavaScript?


Jak mogę przechodzić przez wszystkie wpisy w tablicy przy użyciu JavaScript?

Myślałem, że to coś takiego:

forEach(instance in theArray)

Gdzie theArray jest moją tablicą, ale wydaje się to nieprawidłowe.


3791
2018-02-17 13:51


pochodzenie


Szukałem tego, ale szukałem forEach  i nie tylko for. jak stwierdzono, w języku c # było trochę inaczej i to mnie zdezorientowało :) - Dante1986
ECMAScript & nbsp; 6 może zawierać konstrukt "for ... of". Widzieć dla ... (MDN), aby uzyskać więcej informacji. Możesz już wypróbować to za pomocą najnowszych wersji Firefoksa. - Slaven Rezic
Array.ForEach jest o około 95% wolniejszy niż dla () dla każdego dla Array w JavaScript. Zobacz ten test wydajności online: jsperf.com/fast-array-foreach przez coderwall.com/p/kvzbpa - molokoloco
Dla wielu sytuacji, że 95% wolniej nie będzie znaczące blog.niftysnippets.org/2012/02/foreach-and-runtime-cost.html - David Sykes
Natomiast w Pyton to jest bardziej wydajny używać funkcji niż używać tradycyjnych pętli. (Rozważ to i < len i i++ może być wykonane przez silnik, a nie przez tłumacza.) - joeytwiddle


Odpowiedzi:


TL; DR

  • Nie używaj for-in chyba że użyjesz go z zabezpieczeniami lub przynajmniej wiesz, dlaczego może cię ugryźć.
  • Twoje najlepsze zakłady są zwykle

    • za for-of pętla (tylko ES2015 +),
    • Array#forEach (spec | MDN) (lub jego krewnych some i takie) (tylko ES5 +),
    • prosty staroświecki for pętla,
    • lub for-in z zabezpieczeniami.

Ale tutaj jest dużo więcej do odkrycia, czytaj dalej ...


JavaScript ma potężną semantykę do zapętlenia się przez tablice i obiekty tablicopodobne. Podzielam odpowiedź na dwie części: Opcje dla prawdziwych tablic i opcje dla rzeczy, które są po prostulubić, tak jak arguments obiekt, inne obiekty iterowalne (ES2015 +), kolekcje DOM i tak dalej.

Szybko zauważę, że możesz korzystać z opcji ES2015 teraz, nawet w silnikach ES5, wg transpiling ES2015 do ES5. Wyszukaj "Transpozycja ES2015" / "Transpozycja ES6", aby uzyskać więcej informacji ...

Okay, spójrzmy na nasze opcje:

W przypadku rzeczywistych tablic

Masz trzy opcje w ECMAScript 5 ("ES5"), wersja najbardziej obecnie obsługiwana, i dwie dodatkowe ECMAScript 2015 ("ES2015", "ES6"):

  1. Posługiwać się forEach i pokrewne (ES5 +)
  2. Użyj prostego for pętla
  3. Posługiwać się for-in  prawidłowo
  4. Posługiwać się for-of (użyj niejawnie iteratora) (ES2015 +)
  5. Użyj iteratora jawnie (ES2015 +)

Detale:

1. Użyj forEach I powiązane

W jakimkolwiek niewyraźnie nowoczesnym środowisku (a więc nie IE8), gdzie masz dostęp do Array funkcje dodane przez ES5 (bezpośrednio lub za pomocą wielofunkcyjnych), możesz użyć forEach (spec | MDN):

var a = ["a", "b", "c"];
a.forEach(function(entry) {
    console.log(entry);
});

forEach akceptuje funkcję zwrotną i, opcjonalnie, wartość do użycia jako this podczas wywoływania tego wywołania zwrotnego (nieużywanego powyżej). Callback jest wywoływany dla każdego wpisu w tablicy, w kolejności, pomijając nieistniejące wpisy w rzadkich tablicach. Chociaż użyłem tylko jednego argumentu powyżej, wywołanie zwrotne jest wywoływane z trzema wartościami: wartością każdego wpisu, indeksem tego wpisu i odwołaniem do tablicy, która jest iterowana (w przypadku, gdy funkcja ta nie jest już przydatna). ).

O ile nie obsługujesz przestarzałych przeglądarek, takich jak IE8 (które pokazuje NetApps z nieco ponad 4% udziałem w rynku w chwili pisania tego tekstu we wrześniu 2016 r.), Możesz z powodzeniem używać forEach na stronie internetowej ogólnego przeznaczenia bez podkładki. Jeśli potrzebujesz obsługi przestarzałych przeglądarek, shimming / polyfilling forEach można łatwo zrobić (szukaj "es5 shim" dla kilku opcji).

forEach ma tę zaletę, że nie musisz zadeklarować indeksowania i zmiennych wartości w zakresie zawierającym, ponieważ są one dostarczane jako argumenty funkcji iteracji, a więc ładnie dopasowane do tej iteracji.

Jeśli martwisz się o koszt runtime wywołania funkcji dla każdego wpisu tablicy, nie bądź; Detale.

Do tego, forEach jest funkcją "zapętlaj je wszystkie", ale ES5 zdefiniował kilka innych użytecznych funkcji "przeprowadź pracę przez macierz i wykonaj różne czynności", w tym:

  • every (przestaje zapętlać się po raz pierwszy, gdy zwracane jest wywołanie zwrotne false albo coś w tym rodzaju)
  • some (przestaje zapętlać się po raz pierwszy, gdy zwracane jest wywołanie zwrotne true lub coś w tym rodzaju)
  • filter (tworzy nową tablicę zawierającą elementy, w których funkcja filtru zwraca true i pomijając te, w których powraca false)
  • map (tworzy nową tablicę z wartości zwracanych przez wywołanie zwrotne)
  • reduce (tworzy wartość przez wielokrotne wywoływanie wywołania zwrotnego, przekazywanie poprzednich wartości, patrz specyfikację dla szczegółów, przydatne do sumowania zawartości tablicy i wielu innych rzeczy)
  • reduceRight (lubić reduce, ale działa w kolejności malejącej zamiast rosnącej)

2. Użyj prostego for pętla

Czasami stare sposoby są najlepsze:

var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
    console.log(a[index]);
}

Jeśli długość tablicy nie zmieni się podczas pętli, a jest to kod wrażliwy na wydajność (mało prawdopodobne), nieco bardziej skomplikowana wersja chwytająca długość z przodu może być malutki trochę szybciej:

var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
    console.log(a[index]);
}

I / lub liczenie wstecz:

var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
    console.log(a[index]);
}

Ale w nowoczesnych silnikach JavaScript rzadko trzeba wyciskać ten ostatni kawałek soku.

W ES2015 i nowszych wersjach możesz ustawić zmienne indeksowe i wartościowe na lokalne for pętla:

let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
}
//console.log(index); // Would cause "ReferenceError: index is not defined"
//console.log(value); // Would cause "ReferenceError: value is not defined"

I kiedy to robisz, nie tylko value ale również index jest odtwarzany dla każdej iteracji pętli, co oznacza, że ​​zamknięcia utworzone w ciele pętli zachowują odniesienie do index (i value) stworzone dla tej konkretnej iteracji:

let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
    divs[index].addEventListener('click', e => {
        alert("Index is: " + index);
    });
}

Gdybyś miał pięć elementów div, uzyskałbyś "Indeks to: 0", jeśli klikniesz pierwszy, a "Indeks: 4", jeśli klikniesz ostatni. To robi nie pracować, jeśli używasz var zamiast let.

3. Użyj for-in  prawidłowo

Dostaniesz ludzi, którzy powiedzą ci, żebyś skorzystał for-in, ale to nie to for-in jest dla. for-in pętle przez przeliczalne właściwości obiektu, a nie indeksy tablicy. Zamówienie nie jest gwarantowane, nawet w ES2015 (ES6). ES2015 definiuje kolejność właściwości obiektu (poprzez [[OwnPropertyKeys]], [[Enumerate]]i rzeczy, które ich używają Object.getOwnPropertyKeys), ale to nie zdefiniuj to for-in będzie śledzić to zamówienie. (Szczegóły w ta inna odpowiedź.)

Mimo to mogą być przydatnym, szczególnie dla rzadki tablice, jeśli zastosujesz odpowiednie zabezpieczenia:

// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
    if (a.hasOwnProperty(key)  &&        // These are explained
        /^0$|^[1-9]\d*$/.test(key) &&    // and then hidden
        key <= 4294967294                // away below
        ) {
        console.log(a[key]);
    }
}

Zwróć uwagę na dwie kontrole:

  1. Że obiekt ma swoje posiadać właściwość o tej nazwie (nie taka, która dziedziczy po prototypie), oraz

  2. To, że klucz jest ciągiem numerycznym base-10 w jego normalnej formie, a jego wartość wynosi <= 2 ^ 32 - 2 (czyli 4 294 967 294). Skąd się bierze ta liczba? Jest to część definicji indeksu tablicowego w specyfikacji. Pozostałe liczby (nie będące liczbami całkowitymi, liczby ujemne, liczby większe od 2 ^ 32 - 2) nie są indeksami tablicowymi. Powód 2 ^ 32 - 2 jest to, że czyni największą wartość indeksu niższą niż 2 ^ 32 - 1, która jest maksymalną wartością tablicy length może mieć. (Np. Długość tablicy mieści się w 32-bitowej liczbie całkowitej bez znaku). (Podpowiada RobGowi za wskazanie w komentarzu na moim blogu że mój poprzedni test nie był całkiem odpowiedni.)

Jest to niewielka część dodatkowego narzutu na iterację pętli na większości tablic, ale jeśli masz rzadki array, może to być bardziej efektywny sposób na pętlę, ponieważ pętle są tworzone tylko dla faktycznie istniejących wpisów. Np. Dla powyższej tablicy wykonujemy trzykrotną pętlę (dla kluczy "0", "10", i "10000"- pamiętajcie, że to struny), a nie 10 001 razy.

Teraz nie będziesz chciał tego pisać za każdym razem, więc możesz umieścić to w swoim zestawie narzędzi:

function arrayHasOwnIndex(array, prop) {
    return array.hasOwnProperty(prop) && /^0$|^[1-9]\d*$/.test(prop) && prop <= 4294967294; // 2^32 - 2
}

A potem użylibyśmy tego tak:

for (key in a) {
    if (arrayHasOwnIndex(a, key)) {
        console.log(a[key]);
    }
}

Lub jeśli interesuje Cię tylko test "wystarczająco dobry dla większości przypadków", możesz go użyć, ale gdy jest blisko, nie jest to poprawne:

for (key in a) {
    // "Good enough" for most cases
    if (String(parseInt(key, 10)) === key && a.hasOwnProperty(key)) {
        console.log(a[key]);
    }
}

4. Użyj for-of (użyj niejawnie iteratora) (ES2015 +)

ES2015 dodaje iteratory do JavaScript. Najprostszym sposobem korzystania z iteratorów jest nowy for-of komunikat. To wygląda tak:

var val;
var a = ["a", "b", "c"];
for (val of a) {
    console.log(val);
}

Wydajność:

za
b
do

Pod kołdrą dostaje się iterator z tablicy i pętli przez nią, pobierając z niej wartości. Nie ma problemu z używaniem for-in ma, ponieważ używa iteratora zdefiniowanego przez obiekt (tablicę), a tablice definiują, że ich iteratory iterują poprzez ich wpisy (nie ich właściwości). w odróżnieniu for-in w ES5 kolejność, w jakiej wpisy są odwiedzane, jest porządkiem numerycznym ich indeksów.

5. Używaj jawnie iteratora (ES2015 +)

Czasami możesz chcieć użyć iteratora wyraźnie. Ty też możesz to zrobić, chociaż jest o wiele bardziej niebezpieczny niż for-of. To wygląda tak:

var a = ["a", "b", "c"];
var it = a.values();
var entry;
while (!(entry = it.next()).done) {
    console.log(entry.value);
}

Iterator jest obiektem zgodnym z definicją Iteratora w specyfikacji. Jego next metoda zwraca nową obiekt wynikowy za każdym razem, gdy to nazwiesz. Obiekt wyniku ma właściwość, done, mówiąc nam, czy to zrobione, i własność value z wartością dla tej iteracji. (done jest opcjonalne, jeśli tak false, value jest opcjonalne, jeśli tak undefined.)

Znaczenie value zależy od iteratora; tablice obsługują (przynajmniej) trzy funkcje, które zwracają iteratory:

  • values(): To jest ten, którego użyłem powyżej. Zwraca iterator, gdzie każdy value jest wpisem tablicy dla tej iteracji ("a", "b", i "c" w przykładzie wcześniej).
  • keys(): Zwraca iterator, gdzie każdy value jest kluczem do tej iteracji (tak dla naszego a powyżej, to by było "0", następnie "1", następnie "2").
  • entries(): Zwraca iterator, gdzie każdy value jest tablicą w formularzu [key, value] dla tej iteracji.

Dla obiektów podobnych do Array

Oprócz prawdziwych tablic, są też tablicowy obiekty, które mają length właściwość i właściwości o nazwach numerycznych: NodeList instancje arguments obiekt itp. W jaki sposób przeglądamy ich zawartość?

Użyj jednej z powyższych opcji dla tablic

Przynajmniej niektóre i być może większość lub nawet wszystkie powyższe metody tablicowe często odnoszą się równie dobrze do obiektów tablicowych:

  1. Posługiwać się forEach i pokrewne (ES5 +)

    Różne funkcje w Array.prototype są "celowo generyczne" i zwykle mogą być używane przez obiekty podobne do tablic Function#call lub Function#apply. (Zobacz Zastrzeżenie dla obiektów hostowanych na końcu tej odpowiedzi, ale jest to rzadki problem.)

    Załóżmy, że chcesz użyć forEach na Node„s childNodes własność. Zrobisz to:

    Array.prototype.forEach.call(node.childNodes, function(child) {
        // Do something with `child`
    });
    

    Jeśli masz zamiar zrobić to dużo, możesz pobrać kopię odwołania do funkcji do zmiennej do ponownego wykorzystania, np .:

    // (This is all presumably in some scoping function)
    var forEach = Array.prototype.forEach;
    
    // Then later...
    forEach.call(node.childNodes, function(child) {
        // Do something with `child`
    });
    
  2. Użyj prostego for pętla

    Oczywiście prosty for Pętla dotyczy obiektów tablicowych.

  3. Posługiwać się for-in  prawidłowo

    for-in z tymi samymi zabezpieczeniami, co w przypadku tablicy, powinno również działać z obiektami tablicowymi; może mieć zastosowanie zastrzeżenie dla obiektów hostowanych pod nr 1 powyżej.

  4. Posługiwać się for-of (użyj niejawnie iteratora) (ES2015 +)

    for-of użyje iteratora dostarczonego przez obiekt (jeśli istnieje); będziemy musieli zobaczyć, jak to się dzieje z różnymi obiektami podobnymi do tablic, szczególnie host-provided. Na przykład specyfikacja NodeList od querySelectorAll został zaktualizowany w celu obsługi iteracji. Specyfikacja dla HTMLCollection od getElementsByTagName nie był.

  5. Użyj iteratora jawnie (ES2015 +)

    Patrz # 4, zobaczymy, jak odtwarzają się iteratory.

Utwórz prawdziwą tablicę

Innym razem możesz przekonwertować obiekt tablicowy na prawdziwą tablicę. Jest to zaskakująco proste:

  1. Użyj slice metoda tablic

    Możemy użyć slice metoda tablic, która podobnie jak inne metody wymienione powyżej jest "celowo generyczna" i dlatego może być używana z obiektami podobnymi do tablic, jak na przykład:

    var trueArray = Array.prototype.slice.call(arrayLikeObject);
    

    Na przykład, jeśli chcemy przekonwertować NodeList w prawdziwą tablicę, możemy to zrobić:

    var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
    

    Zobacz Zastrzeżenie dla obiektów hostowanych poniżej. W szczególności zwróć uwagę, że to się nie powiedzie w IE8 i wcześniejszych wersjach, które nie pozwalają ci używać obiektów dostarczanych przez hosta jako this tak.

  2. Posługiwać się rozłożona składnia (...)

    Możliwe jest również użycie ES2015 rozpowszechniać składnię z silnikami JavaScript obsługującymi tę funkcję:

    var trueArray = [...iterableObject];
    

    Na przykład, jeśli chcemy przekonwertować NodeList w prawdziwą tablicę, z rozłożoną składnią staje się ona dość zwięzła:

    var divs = [...document.querySelectorAll("div")];
    
  3. Posługiwać się Array.from  (spec) | (MDN)

    Array.from (ES2015 +, ale łatwo pofałdowany) tworzy tablicę z obiektu tablicopodobnego, opcjonalnie przekazując wpisy przez funkcję odwzorowania jako pierwszą. Więc:

    var divs = Array.from(document.querySelectorAll("div"));
    

    Lub jeśli chcesz uzyskać tablicę nazw znaczników elementów z daną klasą, możesz użyć funkcji mapowania:

    // Arrow function (ES2015):
    var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
    
    // Standard function (since `Array.from` can be shimmed):
    var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
        return element.tagName;
    });
    

Zastrzeżenie dla obiektów hostowanych

Jeśli użyjesz Array.prototype funkcje z host-provided obiekty tablicowe (listy DOM i inne rzeczy dostarczane przez przeglądarkę zamiast mechanizmu JavaScript), należy upewnić się, aby przetestować w środowiskach docelowych, aby upewnić się, że obiekt hosta zachowuje się prawidłowo. Większość zachowuje się właściwie (teraz), ale ważne jest, aby przetestować. Powodem jest to, że większość Array.prototype metody, na które prawdopodobnie będziesz chciał polegać, polegają na obiekcie dostarczonym przez hosta, co daje uczciwą odpowiedź na streszczenie [[HasProperty]] operacja. W chwili pisania tego tekstu przeglądarki bardzo dobrze sobie z tym radzą, ale specyfikacja 5.1 pozwoliła na to, że obiekt hosta może nie być uczciwy. To jest w §8.6.2kilka akapitów poniżej dużej tabeli w pobliżu początku tej sekcji), gdzie jest napisane:

Obiekty hosta mogą implementować te wewnętrzne metody w jakikolwiek sposób, chyba że podano inaczej; na przykład jedną z możliwości jest to [[Get]] i [[Put]] dla konkretnego obiektu hosta rzeczywiście pobiera i przechowuje wartości właściwości, ale [[HasProperty]] zawsze generuje fałszywy.

(Nie mogłem znaleźć ekwiwalentnego słownictwa w specyfikacji ES2015, ale nadal będzie tak być.) Ponownie, poczynając od pisania tych napisów, we wspólnych przeglądarkach udostępniamy obiekty podobne do obiektów hosta [NodeList instancje, na przykład] zrobić uchwyt [[HasProperty]] poprawnie, ale trzeba to przetestować.)


5980
2018-02-17 13:53



Chciałbym również to dodać .forEach nie można skutecznie złamać. Musisz rzucić wyjątek, aby wykonać przerwę. - Pijusn
@Pius: Jeśli chcesz przerwać pętlę, możesz użyć some. (Wolałbym pozwolić na złamanie forEach również, ale oni, nie pytali mnie. ;-)) - T.J. Crowder
@ T.J.Crowder Prawda, mimo że wygląda bardziej na obejście, ponieważ nie jest to jego główny cel. - Pijusn
@ user889030: Potrzebujesz , po k=0nie a ;. Pamiętaj, programowanie to wiele rzeczy, z których jedną jest baczna dbałość o detale ... :-) - T.J. Crowder
@ JimB: To omówiono powyżej (i length nie jest metodą). :-) - T.J. Crowder


Edytować: Ta odpowiedź jest beznadziejnie nieaktualna. Aby uzyskać bardziej nowoczesne podejście, spójrz na metody dostępne w tablicy. Interesujące mogą być:

  • dla każdego
  • mapa
  • filtr
  • zamek błyskawiczny
  • zmniejszyć
  • każdy
  • trochę

Standardowy sposób na iterację tablicy JavaScript to wanilia for-pętla:

var length = arr.length,
    element = null;
for (var i = 0; i < length; i++) {
  element = arr[i];
  // Do something with element i.
}

Zauważ jednak, że to podejście jest dobre tylko wtedy, gdy masz gęstą tablicę, a każdy indeks jest zajęty przez element. Jeśli tablica jest rzadka, możesz natknąć się na problemy z wydajnością z tym podejściem, ponieważ będziesz sprawdzać wiele indeksów, które nie naprawdę istnieje w tablicy. W takim przypadku, a for .. in-loop może być lepszym pomysłem. jednak, musisz użyć odpowiednich zabezpieczeń, aby zapewnić, że działają tylko pożądane właściwości tablicy (to znaczy elementów tablicy), ponieważ for..in-lub zostanie również wyliczony w starszych przeglądarkach lub jeśli dodatkowe właściwości są zdefiniowane jako enumerable.

W ECMAScript 5 na prototypie macierzy pojawi się metoda forEach, ale nie jest ona obsługiwana w starszych przeglądarkach. Aby móc go używać konsekwentnie, musisz albo mieć środowisko, które je obsługuje (na przykład Node.js dla JavaScript po stronie serwera) lub użyj "Polyfill". Polyfill dla tej funkcjonalności jest jednak trywialny i ponieważ sprawia, że ​​kod jest łatwiejszy do odczytania, jest dobrym polyfillem.


451
2018-02-17 13:55



dlaczego jest for(instance in objArray)  nie jest prawidłowym użytkowaniem? wydaje mi się to prostsze, ale słyszę, że mówisz o tym, że nie jest to poprawny sposób użycia? - Dante1986
Możesz użyć buforowania o długości śródliniowej: dla (var i = 0, l = arr.length; i <l; i ++) - Robert
Czy przecinek na końcu pierwszej linii jest zamierzony, czy może to literówka (może to być średnik)? - 9KSoft
@ Wardha-Web Jest celowy. Pozwala nam zadeklarować wiele zmiennych za pomocą jednego var-słowo. Jeśli użylibyśmy średnika, to element zostałby zadeklarowany w skali globalnej (a raczej JSHint krzyczałby na nas przed osiągnięciem produkcji). - PatrikAkerstrand


Jeśli używasz jQuery biblioteka, możesz użyć jQuery.each:

$.each(yourArray, function(index, value) {
  // do your stuff here
});

EDYTOWAĆ : 

Jak na pytanie, użytkownik chce kodu w javascript zamiast jquery, więc edycja jest

var length = yourArray.length;   
for (var i = 0; i < length; i++) {
  // Do something with yourArray[i].
}

205
2018-02-17 14:01



Prawdopodobnie będę korzystał z tej odpowiedzi najczęściej. Nie jest to najlepsza odpowiedź na pytanie, ale w praktyce będzie najprostsza i najbardziej odpowiednia dla tych z nas korzystających z jQuery. Sądzę, że wszyscy powinniśmy także uczyć się drogi waniliowej. Nigdy nie boli, aby rozszerzyć swoje zrozumienie. - mopsyd
Tylko ze względu na to: jQuery każdy jest znacznie wolniejszy niż rozwiązania natywne. Zaleca się, aby jQuery używał natywnego JavaScript zamiast jQuery, gdy jest to możliwe. jsperf.com/browser-diet-jquery-each-vs-for-loop - Kevin Boss
Powstrzymaj się od używania jQuery, gdy możesz użyć wanilii js - Noe
Trzymaj się standardowego JS, przechowuj biblioteki innych firm poza odpowiedzią, chyba że nie ma rozwiązania w języku ojczystym - Steve K
Rodzaj przypomina mi o tym: i.stack.imgur.com/ssRUr.gif - Ajedi32


Pętla do tyłu

Myślę że rewers dla pętli zasługuje na wzmiankę tutaj:

for (var i = array.length; i--; ) {
     // process array[i]
}

Zalety:

  • Nie musisz zadeklarować tymczasowego len zmienna lub porównaj z array.length w każdej iteracji, z których każda może być drobną optymalizacją.
  • Usuwanie rodzeństwa z DOM w odwrotnej kolejności jest zwykle bardziej wydajny. (Przeglądarka musi wykonać mniej przesuwania elementów w swoich wewnętrznych tablicach).
  • Jeśli ty zmodyfikuj tablicę podczas pętli, w indeksie lub po nim ja (na przykład możesz usunąć lub wstawić przedmiot w array[i]), a następnie pętla do przodu pomija pozycję przesuniętą w lewo na pozycję jalub ponownie przetworzyć japrzedmiot przesunięty w prawo. W tradycyjnej pętli for you mógłby aktualizacja ja wskazać następny element, który wymaga przetwarzania - 1, ale po prostu odwrócenie kierunku iteracji jest często a prostsze i bardziej eleganckie rozwiązanie.
  • Podobnie, podczas modyfikowania lub usuwania zagnieżdżony Elementy DOM, przetwarzanie w odwrotnej puszce obejść błędy. Na przykład rozważ modyfikowanie wewnętrznegoHTML węzła nadrzędnego przed obsługą jego elementów podrzędnych. Do czasu, gdy węzeł podrzędny zostanie osiągnięty, zostanie on odłączony od DOM, który został zastąpiony nowopowstałym dzieckiem, gdy zapisano innerHTML rodzica.
  • To jest krótszy wpisać, a czytać, niż niektóre inne dostępne opcje. Chociaż traci forEach() i do ES6 for ... of.

Niedogodności:

  • Przetwarza elementy w odwrotnej kolejności. Jeśli budowałeś nową tablicę z wyników lub drukowałeś rzeczy na ekranie, naturalnie wyjście zostanie odwrócone w odniesieniu do pierwotnego zamówienia.
  • Wielokrotne wstawianie rodzeństwa do DOM jako pierwsze dziecko w celu zachowania ich kolejności jest mniej wydajny. (Przeglądarka będzie musiała przesuwać wszystko poprawnie.) Aby efektywnie tworzyć węzły DOM i po kolei, po prostu przechodź do przodu i dołączaj jak zwykle (a także użyj "fragmentu dokumentu").
  • Pętla odwrotna jest mylące młodszym programistom. (Możesz uznać tę przewagę, w zależności od twojego spojrzenia.)

Czy zawsze powinienem z niego korzystać?

Niektórzy programiści używają pętli odwrotnej dla pętli domyślnie, chyba że istnieje dobry powód, aby pętla została przekazana dalej.

Chociaż przyrost wydajności jest zazwyczaj nieznaczny, to jest to rodzaj krzyku:

"Po prostu rób to dla każdej pozycji na liście, nie dbam o zamówienie!"

Jednak w praktyce tak jest nie w rzeczywistości wiarygodne wskazanie zamiaru, ponieważ nie można go odróżnić od tych okazji, kiedy ty zrobić dbaj o porządek i naprawdę potrzeba pętli do tyłu. Tak więc w rzeczywistości potrzebna byłaby inna konstrukcja, aby dokładnie wyrazić intencję "nie obchodzi", coś obecnie niedostępnego w większości języków, w tym ECMAScript, ale które można nazwać, na przykład, forEachUnordered().

Jeśli zamówienie nie ma znaczenia, i wydajność jest problemem (w najbardziej wewnętrznej pętli gry lub silnika animacji), wtedy może być dopuszczalne użycie odwrotnej pętli jako wzorca. Pamiętaj tylko, że widzisz wstecz w pętli w istniejącym kodzie niekoniecznie znaczy że kolejność nie ma znaczenia!

Lepiej jest użyć forEach ()

Ogólnie dla kodu wyższego poziomu gdzie jasność i bezpieczeństwo są większe problemy, polecam używanie Array::forEach jako domyślny wzór:

  • Jest jasne, aby przeczytać.
  • To wskazuje na to ja nie zostanie przesunięty w obrębie bloku (co zawsze jest możliwą niespodzianką ukrywającą się długo) for i while pętle.)
  • Daje ci to darmowy zakres dla zamknięć.
  • Zmniejsza wyciek zmiennych lokalnych i przypadkowe zderzenie ze zmiennymi zewnętrznymi (i mutacjami).

Następnie, gdy zobaczysz w kodzie zwrotną pętlę, jest to wskazówka, że ​​jest ona odwrócona z dobrego powodu (być może z jednego z powodów opisanych powyżej). A oglądanie tradycyjnej pętli do przodu może oznaczać, że może nastąpić zmiana biegów.

(Jeśli dyskusja na temat intencji nie ma dla ciebie sensu, wtedy ty i twój kod możecie czerpać korzyści z oglądania wykładu Crockforda Styl programowania i twój mózg.)


Jak to działa?

for (var i = 0; i < array.length; i++) { ... }   // Forwards

for (var i = array.length; i--; )    { ... }   // Reverse

Zauważysz to i-- jest środkową klauzulą ​​(gdzie zwykle widzimy porównanie), a ostatnia klauzula jest pusta (gdzie zwykle widzimy i++). Oznacza to, że i-- jest również używany jako stan do kontynuacji. Co najważniejsze, jest on wykonywany i sprawdzany przed każda iteracja.

  • Jak zacząć od array.length bez eksplozji?

    Bo i-- działa przed każda iteracja, w pierwszej iteracji będziemy faktycznie dostęp do elementu na array.length - 1 co pozwala uniknąć problemów z Array-out-of-bound  undefined przedmiotów.

  • Dlaczego nie zatrzymuje się iterowanie przed indeksem 0?

    Pętla przestanie iterować po spełnieniu warunku i-- ocenia na wartość falsey (gdy daje 0).

    Sztuczka polega na tym, że w przeciwieństwie do --i, spływ i-- dekrety operatora i ale daje wartość przed ubytek. Twoja konsola może to demonstrować:

    > var i = 5; [i, i--, i];

    [5, 5, 4]

    Więc w końcowej iteracji, ja był wcześniej 1 i i-- wyrażenie zmienia to na 0 ale faktycznie ustępuje 1 (prawda), a więc warunek mija. W następnej iteracji i-- zmiany ja do -1 ale plony 0 (falsey), powodując, że wykonanie natychmiast spadło z dna pętli.

    W tradycyjnym forwardowaniu pętli, i++ i ++i są wymienne (jak wskazuje Douglas Crockford). Jednak w odwrotnej pętli, ponieważ nasza dekrementacja jest również naszym warunkiem ekspresji, musimy się trzymać i-- jeśli chcemy przetworzyć przedmiot o indeksie 0.


Drobnostki

Niektórzy lubią rysować małą strzałkę na odwrocie for pętla i zakończ z mrugnięciem:

for (var i = array.length; i --> 0 ;) {

Kredyty trafiają do WYL za pokazanie mi korzyści i horrorów odwrotnej pętli.


88
2018-05-02 14:21



Zapomniałem dodać benchmarki. Zapomniałem też wspomnieć, że odwrotna pętla jest znaczącą optymalizacją dla 8-bitowych procesorów takich jak 6502, gdzie naprawdę dostajesz porównanie za darmo! - joeytwiddle
Co powiesz na to w odwrotnej pętli? var i = array.length; podczas gdy ja--) { ... - Kabb5
Sorry @ Kabb5 Byłem AFK. Tak, ta pętla jest całkiem jasna i zasadniczo taka sama. - joeytwiddle
Ach, dobry stary wzór kodu "Strzałka i Wink". Kocham to! +1 ode mnie! - Matheus Avellar
Ta sama odpowiedź jest podana dużo bardziej zwięźle tutaj (w innym pytaniu). - joeytwiddle


Trochę doużywanie języków w stylu foreach pętli poprzez wyliczenia. W JavaScript jest to wykonywane z for..in struktura pętli:

var index,
    value;
for (index in obj) {
    value = obj[index];
}

Jest w tym jakiś haczyk. for..in przejdzie przez każdy z wyliczalnych członków obiektu i członków na swoim prototypie. Aby uniknąć odczytu wartości dziedziczonych przez prototyp obiektu, po prostu sprawdź, czy właściwość należy do obiektu:

for (i in obj) {
    if (obj.hasOwnProperty(i)) {
        //do stuff
    }
}

Do tego, ECMAScript 5 dodał forEach metoda do Array.prototypektóra może być użyta do wyliczenia na tablicy za pomocą calback (polyfill jest w dokumentach, więc nadal możesz go używać w starszych przeglądarkach):

arr.forEach(function (val, index, theArray) {
    //do stuff
});

Ważne jest, aby to zauważyć Array.prototype.forEach nie łamie się, gdy oddzwoni się ponownie false. jQuery i Underscore.js podać własne odmiany each aby zapewnić pętle, które mogą być zwarte.


71
2018-02-17 14:00



Jak więc wyrwać się z pętli foreach ECMAScript5, tak jak w przypadku zwykłej pętli lub pętli foreach takiej, jak w językach w stylu C? - Ciaran Gallagher
@ CiaranG, w JavaScript często można zobaczyć each metody, które pozwalają return false do użycia w celu wyjścia z pętli, ale z forEach to nie jest opcja. Można użyć flagi zewnętrznej (np if (flag) return;, ale uniemożliwiłoby to wykonanie pozostałej części ciała funkcyjnego, forEach nadal będzie kontynuował iterację całej kolekcji. - zzzzBov


Jeśli chcesz przechwycić tablicę, użyj standardowej trzyczęściowej for pętla.

for (var i = 0; i < myArray.length; i++) {
    var arrayItem = myArray[i];
}

Możesz uzyskać optymalizację wydajności poprzez buforowanie myArray.length lub powtarzanie go wstecz.


30
2018-02-17 13:55



dla (var i = 0, length = myArray.length; i <length; i ++) powinno to zrobić - Edson Medina
@EdsonMedina To również utworzy nową zmienną globalną o nazwie length. ;) - joeytwiddle
@joeytwiddle Tak, ale to wykracza poza zakres tego wpisu. W każdym razie utworzysz zmienną globalną. - Edson Medina
@EdsonMedina Moje przeprosiny, miałem to całkowicie błędne. Za pomocą , po wykonaniu zadania nie wprowadzić nowy globalny, więc Twoja sugestia jest po prostu w porządku! Mylę to dla innego problemu: Używanie = po zleceniu tworzy nowy globalny. - joeytwiddle
Uważaj na zmienną i nie lokalnie do pętli. JavaScript nie ma zasięgu bloku. Prawdopodobnie lepiej jest zadeklarować var i, length, arrayItem; przed pętlą, aby uniknąć tego nieporozumienia. - James


ZA dla każdego realizacja (zobacz w jsFiddle):

function forEach(list,callback) {
  var length = list.length;
  for (var n = 0; n < length; n++) {
    callback.call(list[n]);
  }
}

var myArray = ['hello','world'];

forEach(
  myArray,
  function(){
    alert(this); // do something
  }
);

25
2018-04-10 00:26



iterator w tym celu wykonuje niepotrzebne obliczenia długości. W idealnym przypadku długość listy należy obliczać tylko jeden raz. - MIdhun Krishna
@MIdhunKrishna Zaktualizowałem moją odpowiedź i jsFiddle, ale uważam, że nie jest to tak proste, jak myślisz. Sprawdź to pytanie - nmoliveira
Pełną i prawidłową implementację można znaleźć tutaj: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - marciowb


Jeśli nie masz nic przeciwko opróżnianiu tablicy:

var x;

while(x = y.pop()){ 

    alert(x); //do something 

}

x będzie zawierał ostatnią wartość y i zostanie usunięty z tablicy. Możesz także użyć shift() który da i usunie pierwszy przedmiot z y.


25
2018-03-10 02:37



Nie działa, jeśli masz rzadką tablicę [1, 2, undefined, 3]. - M. Grzywaczewski
... lub w rzeczywistości wszystko falsey: [1, 2, 0, 3] lub [true, true, false, true] - joeytwiddle