TL; DR
Ma c'è molto altro da esplorare, continua a leggere ...
JavaScript ha una potente semantica per il looping di matrici e oggetti simili a array. Ho diviso la risposta in due parti: opzioni per array autentici e opzioni per elementi che sono solo simili a array , come l' arguments
oggetto, altri oggetti iterabili (ES2015 +), raccolte DOM e così via.
Presto noterò che ora puoi usare le opzioni ES2015 , anche sui motori ES5, trasferendo ES2015 in ES5. Cerca "ES2015 transpiling" / "ES6 transpiling" per ulteriori informazioni ...
Ok, diamo un'occhiata alle nostre opzioni:
Per array effettivi
Esistono tre opzioni in ECMAScript 5 ("ES5"), la versione più ampiamente supportata al momento, e altre due aggiunte in ECMAScript 2015 ("ES2015", "ES6"):
- Utilizzo
forEach
e relativi (ES5 +)
- Usa un semplice
for
ciclo
- Utilizzare
for-in
correttamente
- Usa
for-of
(usa implicitamente un iteratore) (ES2015 +)
- Utilizzare esplicitamente un iteratore (ES2015 +)
Dettagli:
1. Uso forEach
e relativi
In qualsiasi ambiente vagamente moderno (quindi, non IE8) in cui hai accesso alle Array
funzionalità aggiunte da ES5 (direttamente o usando i polyfill), puoi usare forEach
( spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
accetta una funzione di callback e, facoltativamente, un valore da usare come this
quando si chiama quel callback (non usato sopra). Il callback viene chiamato per ogni voce dell'array, in ordine, saltando le voci inesistenti negli array sparsi. Anche se ho usato solo un argomento sopra, il callback è chiamato con tre: Il valore di ogni voce, l'indice di quella voce e un riferimento all'array su cui stai ripetendo (nel caso in cui la tua funzione non lo abbia già a portata di mano ).
A meno che tu non supporti browser obsoleti come IE8 (che NetApps mostra con una quota di mercato di poco superiore al 4% al momento della stesura di questo scritto a settembre 2016), puoi tranquillamente utilizzarli forEach
in una pagina web per uso generico senza uno spessore. Se è necessario supportare browser obsoleti, lo shimming / polyfilling forEach
è facilmente eseguibile (cercare "shim es5" per diverse opzioni).
forEach
ha il vantaggio di non dover dichiarare le variabili di indicizzazione e di valore nell'ambito di contenimento, poiché vengono fornite come argomenti alla funzione di iterazione e sono così ben mirate a tale iterazione.
Se sei preoccupato per il costo di runtime di effettuare una chiamata di funzione per ogni voce dell'array, non esserlo; dettagli .
Inoltre, forEach
è la funzione "loop through them all", ma ES5 ha definito diverse altre utili funzioni "work through the array and do things", tra cui:
every
(interrompe il looping la prima volta che il callback ritorna false
o qualcosa di falso)
some
(interrompe il looping la prima volta che il callback ritorna true
o qualcosa di vero)
filter
(crea un nuovo array includendo elementi in cui la funzione filtro ritorna true
e omette quelli in cui ritorna false
)
map
(crea un nuovo array dai valori restituiti dal callback)
reduce
(crea un valore chiamando ripetutamente il callback, passando i valori precedenti; vedere le specifiche per i dettagli; utile per sommare il contenuto di un array e molte altre cose)
reduceRight
(come reduce
, ma funziona in ordine decrescente anziché crescente)
2. Utilizzare un semplice for
ciclo
A volte i vecchi modi sono i migliori:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Se la lunghezza della matrice non cambierà durante il ciclo, ed è nel codice prestazioni sensibili (improbabile), una versione un po 'più complicato afferrare la lunghezza in attacco potrebbe essere un piccolo po' più veloce:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
E / o contando all'indietro:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Ma con i moderni motori JavaScript, è raro che tu abbia bisogno di eliminare quell'ultimo po 'di succo.
In ES2015 e versioni successive, è possibile rendere le variabili dell'indice e del valore locali nel for
loop:
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"
}
E quando lo fai, non solo value
ma index
viene anche ricreato per ogni iterazione di loop, il che significa che le chiusure create nel corpo del loop mantengono un riferimento a index
(e value
) creato per quella iterazione specifica:
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>
Se avessi cinque div, otterrai "Indice è: 0" se fai clic sul primo e "Indice è: 4" se fai clic sull'ultimo. Questo non funziona se si utilizza var
invece di let
.
3. Utilizzare for-in
correttamente
Avrai gente che ti dice di usare for-in
, ma non for-in
è per questo . for-in
scorre attraverso le proprietà enumerabili di un oggetto , non gli indici di un array. L'ordine non è garantito , nemmeno in ES2015 (ES6). ES2015 + definisce un ordine per obiettare le proprietà (tramite [[OwnPropertyKeys]]
, [[Enumerate]]
e cose che le usano come Object.getOwnPropertyKeys
), ma non ha definito che for-in
seguirebbe quell'ordine; ES2020 invece lo ha fatto. (Dettagli in questa altra risposta .)
Gli unici casi d'uso reali per for-in
su un array sono:
- E 'un sparse array con enormi lacune in essa, o
- Stai usando proprietà non elementali e vuoi includerle nel loop
Guardando solo quel primo esempio: puoi usare for-in
per visitare quegli elementi di array sparsi se usi le protezioni appropriate:
// `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]);
}
}
Nota i tre controlli:
Che l'oggetto ha una sua proprietà con quel nome (non uno che eredita dal suo prototipo), e
Che la chiave è costituita da tutte le cifre decimali (ad es. Forma di stringa normale, non notazione scientifica) e
Che il valore della chiave quando viene forzato su un numero è <= 2 ^ 32 - 2 (che è 4.294.967.294). Da dove viene quel numero? Fa parte della definizione di un indice di array nella specifica . Altri numeri (non interi, numeri negativi, numeri maggiori di 2 ^ 32 - 2) non sono indici di array. Il motivo per cui è 2 ^ 32 - 2 è che rende il valore dell'indice più grande uno inferiore a 2 ^ 32 - 1 , che è il valore massimo che length
può avere un array . (Ad esempio, la lunghezza di un array si adatta a un numero intero senza segno a 32 bit.) (Propone a RobG per aver sottolineato in un commento sul mio post sul blog che il mio test precedente non era corretto.)
Non lo faresti nel codice in linea, ovviamente. Scriveresti una funzione di utilità. Forse:
// 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. Usa for-of
(usa implicitamente un iteratore) (ES2015 +)
ES2015 ha aggiunto iteratori a JavaScript. Il modo più semplice per usare gli iteratori è la nuova for-of
affermazione. Sembra così:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Sotto le copertine, ottiene un iteratore dall'array e lo attraversa, ottenendo i valori da esso. Questo non ha il problema che l'utilizzo for-in
ha, perché usa un iteratore definito dall'oggetto (l'array) e gli array definiscono che i loro iteratori iterano attraverso le loro voci (non le loro proprietà). A differenza for-in
di ES5, l'ordine in cui le voci vengono visitate è l'ordine numerico dei loro indici.
5. Utilizzare esplicitamente un iteratore (ES2015 +)
A volte, potresti voler usare un iteratore esplicitamente . Puoi farlo anche tu, anche se è molto più clunkier di for-of
. Sembra così:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
L'iteratore è un oggetto che corrisponde alla definizione Iteratore nelle specifiche. Il suo next
metodo restituisce un nuovo oggetto risultato ogni volta che lo chiami. L'oggetto risultato ha una proprietà done
che ci dice se è stato fatto e una proprietà value
con il valore per quell'iterazione. ( done
è facoltativo se lo fosse false
, value
è facoltativo se lo fosse undefined
.)
Il significato di value
varia a seconda dell'iteratore; le matrici supportano (almeno) tre funzioni che restituiscono iteratori:
values()
: Questo è quello che ho usato sopra. Esso restituisce un iteratore in cui ogni value
è la voce di array per quella iterazione ( "a"
, "b"
, e "c"
nell'esempio precedente).
keys()
: Restituisce un iteratore in cui ognuno value
è la chiave per quell'iterazione (quindi per quanto a
sopra, sarebbe "0"
, quindi "1"
, quindi "2"
).
entries()
: Restituisce un iteratore in cui ognuno value
è un array nel modulo [key, value]
per quell'iterazione.
Per oggetti simili a array
Oltre alle vere matrici, ci sono anche oggetti simili a array che hanno una length
proprietà e proprietà con nomi numerici: NodeList
istanze, l' arguments
oggetto, ecc. Come possiamo scorrere il loro contenuto?
Utilizzare una delle opzioni sopra per gli array
Almeno alcuni, e forse la maggior parte o anche tutti, degli approcci di array sopra spesso si applicano ugualmente bene ad oggetti simili a array:
Utilizzo forEach
e relativi (ES5 +)
Le varie funzioni Array.prototype
sono "intenzionalmente generiche" e di solito possono essere utilizzate su oggetti simili a array tramite Function#call
o Function#apply
. (Vedi gli avvertimenti per gli oggetti forniti dall'host alla fine di questa risposta, ma è un problema raro.)
Si supponga di voler utilizzare forEach
su una Node
's childNodes
proprietà. Faresti questo:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Se hai intenzione di farlo molto, potresti prendere una copia del riferimento della funzione in una variabile per riutilizzarla, ad esempio:
// (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`
});
Usa un semplice for
ciclo
Ovviamente, un semplice for
ciclo si applica agli oggetti simili a array.
Utilizzare for-in
correttamente
for-in
con le stesse garanzie di un array dovrebbe funzionare anche con oggetti simili a array; può essere applicato il avvertimento per gli oggetti forniti dall'host sul n. 1 sopra.
Usa for-of
(usa implicitamente un iteratore) (ES2015 +)
for-of
utilizza l' iteratore fornito dall'oggetto (se presente). Ciò include oggetti forniti dall'host. Ad esempio, le specifiche per il NodeList
da sono querySelectorAll
state aggiornate per supportare l'iterazione. Le specifiche per il HTMLCollection
da getElementsByTagName
non lo erano.
Utilizzare esplicitamente un iteratore (ES2015 +)
Vedi # 4.
Crea un array vero
Altre volte, potresti voler convertire un oggetto simile ad un array in un vero array. Fare ciò è sorprendentemente facile:
Usa il slice
metodo degli array
Possiamo usare il slice
metodo degli array, che come gli altri metodi sopra menzionati è "intenzionalmente generico" e quindi può essere usato con oggetti simili ad array, come questo:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Quindi, ad esempio, se vogliamo convertire un NodeList
in un vero array, potremmo farlo:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Vedi le avvertenze per gli oggetti forniti dall'host di seguito. In particolare, si noti che ciò non funzionerà in IE8 e precedenti, il che non consente di utilizzare oggetti forniti dall'host in this
questo modo.
Usa sintassi diffusa ( ...
)
È anche possibile utilizzare la sintassi diffusa di ES2015 con motori JavaScript che supportano questa funzione. Ad esempio for-of
, utilizza l' iteratore fornito dall'oggetto (vedere # 4 nella sezione precedente):
var trueArray = [...iterableObject];
Quindi, per esempio, se vogliamo convertire un NodeList
in un vero array, con la sintassi diffusa questo diventa abbastanza sintetico:
var divs = [...document.querySelectorAll("div")];
Uso Array.from
Array.from
(spec) | (MDN) (ES2015 +, ma facilmente polifilabile) crea un array da un oggetto simile a un array, opzionalmente passando prima le voci attraverso una funzione di mappatura. Così:
var divs = Array.from(document.querySelectorAll("div"));
Oppure, se desideri ottenere una matrice dei nomi dei tag degli elementi con una determinata classe, dovresti utilizzare la funzione di mappatura:
// 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;
});
Avvertenza per oggetti forniti dall'host
Se si utilizzano Array.prototype
funzioni con oggetti simili a array forniti dall'host (elenchi DOM e altri elementi forniti dal browser anziché dal motore JavaScript), è necessario assicurarsi di testare negli ambienti di destinazione per assicurarsi che l'oggetto fornito dall'host si comporti correttamente . La maggior parte si comporta correttamente (ora), ma è importante testarlo. Il motivo è che la maggior parte dei Array.prototype
metodi che probabilmente vorrai utilizzare si basano sull'oggetto fornito dall'host che fornisce una risposta onesta [[HasProperty]]
all'operazione astratta . Al momento della stesura di questo documento, i browser fanno un ottimo lavoro, ma la specifica 5.1 ha consentito la possibilità che un oggetto fornito dall'host non sia onesto. È in §8.6.2 , diversi paragrafi sotto il grande tavolo vicino all'inizio di quella sezione), dove dice:
Gli oggetti host possono implementare questi metodi interni in qualsiasi modo se non diversamente specificato; per esempio, una possibilità è che [[Get]]
e [[Put]]
per un particolare oggetto host effettivamente recuperi e memorizzi valori di proprietà ma [[HasProperty]]
genera sempre false .
(Non sono riuscito a trovare la verbosità equivalente nella ES2015 specifiche, ma è destinata a essere ancora il caso.) Anche in questo caso, al momento della stesura del comune host fornito array come oggetti in browser moderni [ NodeList
casi, per esempio] fare manico [[HasProperty]]
correttamente, ma è importante testarlo.)
forEach
e non solofor
. come detto, in c # era un po 'diverso, e questo mi ha confuso :)