TL; DR
Ale jest o wiele więcej do odkrycia, czytaj dalej ...
JavaScript ma potężną semantykę do zapętlania tablic i obiektów podobnych do tablic. Podzieliłem odpowiedź na dwie części: Opcje dla prawdziwych tablic i opcje dla rzeczy, które są po prostu podobne do tablic , takich jak arguments
obiekt, inne iterowalne obiekty (ES2015 +), kolekcje DOM i tak dalej.
Ja szybko zauważyć, że można korzystać z opcji ES2015 teraz , nawet w silnikach ES5 przez transpiling ES2015 do ES5. Wyszukaj „ES2015 transpiling” / „ES6 transpiling”, aby uzyskać więcej ...
Ok, spójrzmy na nasze opcje:
Dla rzeczywistych tablic
Masz trzy opcje w ECMAScript 5 („ES5”), najbardziej obecnie obsługiwana wersja, i dwie kolejne dodane w ECMAScript 2015 („ES2015”, „ES6”):
- Zastosowanie
forEach
i powiązane (ES5 +)
- Użyj prostej
for
pętli
- Używaj
for-in
poprawnie
- Użyj
for-of
(użyj pośrednio iteratora) (ES2015 +)
- Użyj jawnie iteratora (ES2015 +)
Detale:
1. Wykorzystanie forEach
i powiązane
W dowolnym nieco nowoczesnym środowisku (a więc nie IE8), w którym masz dostęp do Array
funkcji dodanych przez ES5 (bezpośrednio lub za pomocą wielopełniaczy), możesz użyć forEach
( spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
akceptuje funkcję wywołania zwrotnego i opcjonalnie wartość, która ma być używana this
podczas wywoływania tego wywołania zwrotnego (nieużywane powyżej). Wywołanie zwrotne jest wywoływane 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ścią każdego wpisu, indeksem tego wpisu i referencją do tablicy, nad którą się iteruje (na wypadek, gdyby twoja funkcja jeszcze tego nie miała) ).
O ile nie obsługujesz przestarzałych przeglądarek, takich jak IE8 (które NetApps pokazuje przy nieco ponad 4% udziale w rynku od tego pisania we wrześniu 2016 r.), Możesz z przyjemnością korzystać forEach
z ogólnej strony internetowej bez cienia wątpliwości . Jeśli potrzebujesz obsługi przestarzałych przeglądarek, forEach
łatwo jest wykonać shimming / polifilling (wyszukaj „es5 shim” dla kilku opcji).
forEach
ma tę zaletę, że nie trzeba deklarować zmiennych indeksujących i wartościujących w zakresie zawierającym, ponieważ są one dostarczane jako argumenty do funkcji iteracji, a więc ładnie obejmują tylko tę iterację.
Jeśli martwisz się kosztami wykonania wywołania funkcji dla każdego wpisu tablicy, nie przejmuj się; szczegóły .
Dodatkowo forEach
jest funkcja „pętli przez wszystkie”, ale w ES5 zdefiniowano kilka innych przydatnych funkcji „przeszukiwania tablicy i robienia rzeczy”, w tym:
every
(przestaje zapętlać się po pierwszym powrocie wywołania zwrotnego false
lub coś falsey)
some
(przestaje zapętlać się po pierwszym powrocie wywołania zwrotnego true
lub coś prawdziwego)
filter
(tworzy nową tablicę zawierającą elementy, do których zwraca funkcja filtru true
i pomija te, które zwraca false
)
map
(tworzy nową tablicę na podstawie wartości zwróconych przez wywołanie zwrotne)
reduce
(buduje wartość poprzez wielokrotne wywoływanie wywołania zwrotnego, przekazywanie poprzednich wartości; zobacz specyfikację dla szczegółów; przydatne do sumowania zawartości tablicy i wielu innych rzeczy)
reduceRight
(jak reduce
, ale działa w kolejności malejącej niż rosnącej)
2. Użyj prostej for
pętli
Czasami najlepsze są stare sposoby:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Jeśli długość tablicy nie ulegnie zmianie podczas pętli, i to w wykonaniu wrażliwych kodu (mało prawdopodobne), nieco bardziej skomplikowana wersja chwytając długości do przodu może być malutki nieco szybciej:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
I / lub odliczanie 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 wyciągać ten ostatni kawałek soku.
W wersji ES2015 i nowszych możesz ustawić zmienne indeksu i wartości na lokalne dla for
pętli:
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
//console.log(index); // would cause "ReferenceError: index is not defined"
//console.log(value); // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
try {
console.log(index);
} catch (e) {
console.error(e); // "ReferenceError: index is not defined"
}
try {
console.log(value);
} catch (e) {
console.error(e); // "ReferenceError: value is not defined"
}
A kiedy to zrobisz, nie tylko value
, ale również index
jest odtworzony na każdej iteracji pętli, czyli zamknięcia utworzone w ciele pętli zachować odniesienie do index
(i value
) utworzona dla tej konkretnej iteracji:
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
Gdybyś miał pięć div, dostaniesz „Index is: 0”, jeśli klikniesz pierwszy i „Index is: 4”, jeśli klikniesz ostatni. To nie działa, jeśli używasz var
zamiast let
.
3. Używaj for-in
poprawnie
Dostaniesz ludzi mówiących ci, żebyś używał for-in
, ale nie po to for-in
jest . for-in
zapętla wyliczalne właściwości obiektu , a nie indeksy tablicy. Zamówienie nie jest gwarantowane , nawet w ES2015 (ES6). ES2015 + nie definiuje zamówienia do właściwości obiektów (przez [[OwnPropertyKeys]]
, [[Enumerate]]
i rzeczy, które ich używają podoba Object.getOwnPropertyKeys
), ale nie określają, że for-in
pójdzie zlecenia; ES2020 jednak. (Szczegóły w tej drugiej odpowiedzi .)
Jedynymi rzeczywistymi przypadkami użycia dla for-in
tablicy są:
- To rzadkie tablice z ogromnymi lukami, lub
- Używasz właściwości nieelementowych i chcesz uwzględnić je w pętli
Patrząc tylko na pierwszy przykład: możesz użyć for-in
do odwiedzenia tych rzadkich elementów tablicy, 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 checks are
/^0$|^[1-9]\d*$/.test(key) && // explained
key <= 4294967294 // below
) {
console.log(a[key]);
}
}
Zwróć uwagę na trzy kontrole:
Że przedmiot ma swój własny obiekt o tej samej nazwie (nie jeden to dziedziczy od swojego prototypu) oraz
Że kluczem są wszystkie cyfry dziesiętne (np. Normalna postać łańcucha, a nie notacja naukowa), i
Wartość klucza po wymuszeniu na liczbę wynosi <= 2 ^ 32 - 2 (czyli 4 294 967 294). Skąd ta liczba? Jest to część definicji indeksu tablicowego w specyfikacji . Inne liczby (liczby całkowite, liczby ujemne, liczby większe niż 2 ^ 32-2) nie są indeksami tablicowymi. Wynika to z tego, że 2 ^ 32 - 2 sprawia, że najwyższa wartość indeksu jest mniejsza niż 2 ^ 32 - 1 , czyli maksymalna wartość, jaką length
może mieć tablica . (Np. Długość tablicy mieści się w 32-bitowej liczbie całkowitej bez znaku). (Proponuje RobGowi wskazanie w komentarzu na moim blogu, że mój poprzedni test nie był całkiem poprawny .)
Oczywiście nie zrobiłbyś tego w kodzie wbudowanym. Napisziłbyś funkcję narzędzia. Być może:
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
4. Użyj for-of
(użyj pośrednio iteratora) (ES2015 +)
ES2015 dodał iteratory do JavaScript. Najłatwiejszym sposobem korzystania z iteratorów jest nowa for-of
instrukcja. To wygląda tak:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Pod osłonami pobiera iterator z tablicy i przechodzi przez nią, uzyskując z niej wartości. Nie ma to problemu, który for-in
ma, ponieważ używa iteratora zdefiniowanego przez obiekt (tablicę), a tablice definiują, że ich iteratory iterują przez swoje wpisy (a nie ich właściwości). Inaczej niż for-in
w ES5, kolejność odwiedzania wpisów to kolejność numeryczna ich indeksów.
5. Użyj jawnie iteratora (ES2015 +)
Czasami możesz chcieć jawnie użyć iteratora . Możesz to również zrobić, chociaż jest to znacznie bardziej niezręczne niż . To wygląda tak:for-of
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
Iterator jest obiektem pasującym do definicji Iterator w specyfikacji. Ta next
metoda zwraca nowy obiekt wyniku za każdym razem, gdy go wywołujesz. Wynikowy obiekt ma właściwość, która done
mówi nam, czy zostało wykonane, oraz właściwość value
o wartości dla tej iteracji. ( done
jest opcjonalny, jeśli byłby false
, value
jest opcjonalny, gdyby był undefined
).
Znaczenie value
różni się w zależności od iteratora; tablice obsługują (co najmniej) trzy funkcje zwracające iteratory:
values()
: Tego użyłem powyżej. Zwraca iteracyjnej, gdzie każdy value
jest wpis tablicy w tej iteracji ( "a"
, "b"
i "c"
w tym przykładzie wcześniej).
keys()
: Zwraca iterator, w którym każdy value
jest kluczem do tej iteracji (więc dla naszego a
powyższego byłoby "0"
to "1"
wtedy "2"
).
entries()
: Zwraca iterator, w którym każdy value
jest tablicą w formie [key, value]
dla tej iteracji.
Dla obiektów podobnych do tablicy
Oprócz prawdziwych tablic istnieją również obiekty podobne do tablic, które mają length
właściwość i właściwości o nazwach numerycznych: NodeList
instancje, arguments
obiekt itp. Jak przeglądamy ich zawartość?
Użyj dowolnej z powyższych opcji dla tablic
Przynajmniej niektóre, a być może większość lub nawet wszystkie powyższe podejścia tablicowe często stosuje się równie dobrze do obiektów podobnych do tablicy:
Zastosowanie forEach
i powiązane (ES5 +)
Różne włączone funkcje Array.prototype
są „celowo ogólne” i zwykle można ich używać na obiektach podobnych do tablicy za pomocą Function#call
lub Function#apply
. (Zobacz zastrzeżenie dotyczące obiektów dostarczonych przez hosta na końcu tej odpowiedzi, ale jest to rzadki problem).
Załóżmy, że chcesz skorzystać forEach
na Node
„s childNodes
nieruchomości. Zrobiłbyś to:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Jeśli zamierzasz to często robić, możesz pobrać kopię odwołania do funkcji do zmiennej w celu 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`
});
Użyj prostej for
pętli
Oczywiście prosta for
pętla dotyczy obiektów podobnych do tablicy.
Używaj for-in
poprawnie
for-in
z tymi samymi zabezpieczeniami, co w przypadku tablicy, powinien również działać z obiektami podobnymi do tablicy; może mieć zastosowanie zastrzeżenie dotyczące obiektów dostarczonych przez hosta w punkcie 1 powyżej.
Użyj for-of
(użyj pośrednio iteratora) (ES2015 +)
for-of
używa iteratora dostarczonego przez obiekt (jeśli istnieje). Obejmuje to obiekty dostarczone przez hosta. Na przykład specyfikacja dla NodeList
od querySelectorAll
została zaktualizowana w celu obsługi iteracji. Specyfikacja dla HTMLCollection
od getElementsByTagName
nie była.
Użyj jawnie iteratora (ES2015 +)
Zobacz # 4.
Utwórz prawdziwą tablicę
Innym razem możesz chcieć przekonwertować obiekt podobny do tablicy na prawdziwą tablicę. Jest to zaskakująco łatwe:
Użyj slice
metody tablic
Możemy użyć slice
metody tablic, która podobnie jak inne metody wspomniane powyżej, jest „celowo ogólna” i dlatego może być stosowana z obiektami podobnymi do tablic, takimi jak to:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Na przykład, jeśli chcemy przekonwertować NodeList
tablicę na prawdziwą, moglibyśmy to zrobić:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Zobacz zastrzeżenie dotyczące obiektów udostępnianych przez hosta poniżej. W szczególności zauważ, że to się nie powiedzie w IE8 i wcześniejszych wersjach, co nie pozwala na używanie obiektów dostarczonych przez hosta w this
ten sposób.
Użyj składni spread ( ...
)
Możliwe jest także użycie składni rozproszonej ES2015 z silnikami JavaScript obsługującymi tę funkcję. Podobnie for-of
, używa iteratora dostarczonego przez obiekt (patrz # 4 w poprzedniej sekcji):
var trueArray = [...iterableObject];
Na przykład, jeśli chcemy przekonwertować NodeList
tablicę na prawdziwą, przy składni rozproszonej staje się to dość zwięzłe:
var divs = [...document.querySelectorAll("div")];
Posługiwać się Array.from
Array.from
(specyfikacja) | (MDN) (ES2015 +, ale łatwo wypełniany) tworzy tablicę z obiektu podobnego do tablicy, opcjonalnie przepuszczając najpierw wpisy przez funkcję odwzorowania. Więc:
var divs = Array.from(document.querySelectorAll("div"));
Lub jeśli chcesz uzyskać tablicę nazw znaczników elementów z daną klasą, skorzystaj z 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 dotyczące obiektów udostępnianych przez hosta
Jeśli używasz Array.prototype
funkcji z obiektami tablicowymi udostępnianymi przez hosta (listami DOM i innymi rzeczami udostępnianymi przez przeglądarkę, a nie z silnika JavaScript), musisz koniecznie przetestować w środowiskach docelowych, aby upewnić się, że obiekt dostarczony przez hosta działa poprawnie . Większość zachowuje się poprawnie (teraz), ale ważne jest, aby przetestować. Powodem jest to, że większość Array.prototype
metod, których prawdopodobnie chcesz użyć, opiera się na obiekcie dostarczonym przez hosta, dając uczciwą odpowiedź na [[HasProperty]]
operację abstrakcyjną . W tym momencie przeglądarki wykonują bardzo dobrą robotę, ale specyfikacja 5.1 pozwoliła na to, że obiekt udostępniony przez hosta może nie być uczciwy. Znajduje się w §8.6.2 , kilka akapitów poniżej dużej tabeli blisko początku tej sekcji), gdzie jest napisane:
Obiekty hosta mogą implementować te metody wewnętrzne w jakikolwiek sposób, chyba że określono inaczej; Na przykład, jedną z możliwości jest, [[Get]]
a [[Put]]
dla danego obiektu gospodarza rzeczywiście pobierania i przechowywania wartości własności, ale [[HasProperty]]
zawsze powoduje fałszywe .
(Nie może znaleźć równoważne słownictwa w ES2015 specyfikacji, ale to wiąże się jeszcze w przypadku). Ponownie, w tym piśmie wspólnego gospodarza pod warunkiem, tablicowej obiektów przeglądarkami [ NodeList
przypadkach, na przykład] wykonania uchwytu [[HasProperty]]
poprawnie, ale ważne jest, aby przetestować).
forEach
i nie tylkofor
. jak powiedziano, w c # było trochę inaczej i to mnie zdezorientowało :)