Come posso fare un ciclo attraverso tutte le voci di un array usando JavaScript?
Pensavo fosse qualcosa del genere:
forEach(instance in theArray)
Dove theArray
è il mio array, ma questo sembra non essere corretto.
TL;DR
for-in
a meno che non lo usiate con delle protezioni o siate almeno consapevoli del perché potrebbe mordervi.for-of
(solo ES2015+),Array#forEach
(spec
| MDN
) (o i suoi parenti some
e simili) (solo ES5+),for
vecchio stile,for-in
con protezioni.
Ma c'è molto di più da esplorare, continuate a leggere...JavaScript ha una potente semantica per il looping attraverso gli array e gli oggetti simili agli array. Ho diviso la risposta in due parti: Opzioni per array autentici, e opzioni per cose che sono solo array-like, come l'oggetto arguments
, altri oggetti iterabili (ES2015+), collezioni DOM, e così via.
Noterò rapidamente che è possibile utilizzare le opzioni ES2015 ora, anche sui motori ES5, transpiling da ES2015 a ES5. Cerca "ES2015 transpiling" / "ES6 transpiling" per saperne di più...
Ok, diamo un'occhiata alle nostre opzioni:
Hai tre opzioni in ECMAScript 5 ("ES5"), la versione più ampiamente supportata al momento, e altre due aggiunte in ECMAScript 2015 ("ES2015", "ES6"):
forEach
e relativi (ES5+)for
.for-in
*correttamentefor-of
(usare un iteratore implicitamente) (ES2015+)forEach
e relativiIn qualsiasi ambiente vagamente moderno (quindi, non IE8) dove si ha accesso alle funzionalità Array
aggiunte da ES5 (direttamente o usando i polyfill), si può usare forEach
(spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
accetta una funzione di callback e, opzionalmente, un valore da usare come this
quando si chiama tale callback (non usato sopra). La callback viene chiamata per ogni voce dell'array, in ordine, saltando le voci inesistenti negli array sparsi. Anche se ho usato solo un argomento sopra, la callback viene chiamata con tre: Il valore di ogni voce, l'indice di quella voce, e un riferimento all'array su cui state iterando (nel caso in cui la vostra funzione non lo abbia già a portata di mano).
A meno che non stiate supportando browser obsoleti come IE8 (che NetApps mostra a poco più del 4% di quota di mercato al momento in cui scriviamo, a settembre 2016), potete tranquillamente usare forEach
in una pagina web general-purpose senza uno shim. Se hai bisogno di supportare browser obsoleti, lo shimming/polyfilling di forEach
è facilmente realizzabile (cerca "es5 shim" per diverse opzioni).
forEach
ha il vantaggio di non dover dichiarare le variabili di indicizzazione e di valore nell'ambito di contenimento, in quanto sono fornite come argomenti alla funzione di iterazione, e quindi ben comprese solo in quell'iterazione.
Se siete preoccupati per il costo di runtime di fare una chiamata di funzione per ogni voce dell'array, non preoccupatevi; dettagli.
Inoltre, forEach
è la funzione "loop through them all", ma ES5 ha definito diverse altre utili "work your way through the array and do things" funzioni, incluse:
every
(ferma il ciclo la prima volta che la callback restituisce false
o qualcosa di falso)some
(ferma il ciclo la prima volta che la callback restituisce true
o qualcosa di vero)filter
(crea un nuovo array includendo gli elementi dove la funzione di filtro restituisce true
e omettendo quelli dove restituisce false
)map
(crea un nuovo array dai valori restituiti dalla callback)reduce
(costruisce un valore chiamando ripetutamente la callback, passando valori precedenti; vedere le specifiche per i dettagli; utile per sommare il contenuto di un array e molte altre cose)reduceRight
(come reduce
, ma lavora in ordine discendente piuttosto che ascendente)for
.A volte i vecchi metodi sono i migliori:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Se la lunghezza dell'array non cambierà durante il ciclo, ed è in un codice sensibile alle prestazioni (improbabile), una versione leggermente più complicata che prende la lunghezza in anticipo potrebbe essere un tiny 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 tirar fuori quell'ultimo po' di succo.
In ES2015 e superiori, potete rendere le vostre variabili indice e valore locali al ciclo for
:
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 valore
ma anche indice
viene ricreato per ogni iterazione del ciclo, cioè le chiusure create nel corpo del ciclo mantengono un riferimento all'indice
(e al valore
) creato per quella specifica iterazione:
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 tu avessi cinque div, otterresti "Indice è: 0" se hai cliccato il primo e "Indice è: 4" se hai cliccato l'ultimo. Questo non funziona se usi var
invece di let
.
for-in
correttamente.La gente ti dirà di usare for-in
, ma non è a questo che serve for-in
. for-in
fa un loop 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 le proprietà degli oggetti (tramite [[OwnPropertyKeys]]
, [[Enumerate]]
, e cose che li usano come Object.getOwnPropertyKeys
), ma non definisce che for-in
seguirà quell'ordine. (Dettagli in quest'altra risposta.
Gli unici veri casi d'uso per for-in
su un array sono:
for-in
per visitare quegli elementi dell'array sparese se usi le opportune precauzioni:
// `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]);
}
}
Notate i tre controlli:
// 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);
});
for-of
(usare un iteratore implicitamente) (ES2015+)ES2015 aggiunge gli iteratori a JavaScript. Il modo più semplice per usare gli iteratori è la nuova istruzione for-of
. Assomiglia a questo:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Sotto le coperte, questo ottiene un iteratore dall'array e lo attraversa in loop, ottenendo i valori da esso. Questo non ha il problema che ha l'uso di for-in
, perché usa un iteratore definito dall'oggetto (l'array), e gli array definiscono che i loro iteratori iterano attraverso i loro entri (non le loro proprietà). A differenza di for-in
in ES5, l'ordine in cui le voci sono visitate è l'ordine numerico dei loro indici.
A volte, potresti voler usare un iteratore esplicitamente. Si può fare anche questo, anche se è molto più complicato di for-of
. Si presenta 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 di Iterator nella specifica. Il suo metodo next
restituisce un nuovo oggetto risultato ogni volta che lo si chiama. L'oggetto risultato ha una proprietà, done
, che ci dice se ha finito, e una proprietà value
con il valore per quell'iterazione. (done
è opzionale se sarebbe false
, value
è opzionale se sarebbe undefined
).
Il significato di value
varia a seconda dell'iteratore; gli array supportano (almeno) tre funzioni che restituiscono iteratori:
valori()
: Questa è quella che ho usato sopra. Restituisce un iteratore dove ogni valore
è la voce dell'array per quell'iterazione ("a"
, "b"
, e "c"
nell'esempio precedente).keys()
: Restituisce un iteratore dove ogni valore
è la chiave per quell'iterazione (quindi per la nostra a
sopra, sarebbe "0"
, poi "1"
, poi "2"
).entries()
: Restituisce un iteratore dove ogni valore
è un array nella forma [key, value]
per quell'iterazione.Oltre ai veri array, ci sono anche oggetti array-like che hanno una proprietà length
e proprietà con nomi numerici: istanze NodeList
, l'oggetto arguments
, ecc. Come facciamo a scorrere il loro contenuto?
Almeno alcuni, e forse la maggior parte o anche tutti, degli approcci agli array di cui sopra spesso si applicano altrettanto bene agli oggetti simili agli array:
Utilizzare forEach
e affini (ES5+)
Le varie funzioni su Array.prototype
sono "intenzionalmente generiche" e di solito possono essere usate su oggetti simili a matrici tramite Function#call
o Function#apply
. (Vedere il Caveat per gli oggetti forniti dall'host alla fine di questa risposta, ma è un problema raro).
Supponiamo che tu voglia usare forEach
su un Node
'proprietà childNodes
. Si farebbe così:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Fai qualcosa con child
});
Se hai intenzione di farlo spesso, potresti voler prendere una copia del riferimento della funzione in una variabile per riutilizzarla, ad es:
// (Questo è tutto presumibilmente in qualche funzione di scoping)
var forEach = Array.prototype.forEach;
// Poi più tardi...
forEach.call(node.childNodes, function(child) {
// Fai qualcosa con child
.
});
Usa un semplice ciclo for
Ovviamente, un semplice ciclo for
si applica agli oggetti di tipo array.
Utilizzare for-in
correttamente
La funzione for-in
con le stesse salvaguardie di una matrice dovrebbe funzionare anche con oggetti simili a matrici; si può applicare l'avvertenza per gli oggetti forniti dall'host su #1.
Usa for-of
(usa un iteratore implicitamente) (ES2015+)
for-of
userà l'iteratore fornito dall'oggetto (se c'è); dovremo vedere come funziona con i vari oggetti tipo array, in particolare quelli forniti dall'host. Per esempio, la specifica per la NodeList
di querySelectorAll
è stata aggiornata per supportare l'iterazione. La specifica per la HTMLCollection
da getElementsByTagName
non lo è stata.
Usa esplicitamente un iteratore (ES2015+) Vedi #4, dobbiamo vedere come si comportano gli iteratori.
Altre volte, potresti voler convertire un oggetto simile ad un array in un vero array. Farlo è sorprendentemente facile:
Utilizzare il metodo slice
degli array
Possiamo usare il metodo slice
degli array, che come gli altri metodi menzionati sopra è "intenzionalmente generico" e quindi può essere usato con oggetti di tipo array, come questo:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Quindi, per esempio, se vogliamo convertire una NodeList
in un vero array, potremmo fare così
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Vedere il Caveat per gli oggetti forniti dall'host qui sotto. In particolare, notate che questo fallirà in IE8 e precedenti, che non vi permettono di usare oggetti forniti dall'host come this
in questo modo.
Usa sintassi di diffusione (...
)
E' anche possibile utilizzare la [sintassi di diffusione] di ES2015 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) con i motori JavaScript che supportano questa caratteristica:
var trueArray = [...iterableObject];
Così, per esempio, se vogliamo convertire una NodeList
in un vero array, con la sintassi spread questo diventa abbastanza succinto:
var divs = [...document.querySelectorAll("div")];
Usa Array.from
(spec) | (MDN)
Array.from
(ES2015+, ma facilmente polyfilled) crea un array da un oggetto simile ad un array, passando opzionalmente le voci attraverso una funzione di mappatura prima. Quindi:
var divs = Array.from(document.querySelectorAll("div"));
O se si volesse ottenere un array dei nomi dei tag degli elementi con una data classe, si potrebbe usare la funzione di mappatura:
// Funzione Arrow (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Funzione standard (dato che Array.from
può essere spalmato):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Se usate le funzioni Array.prototype
con oggetti tipo array forniti dall'host (elenchi DOM e altre cose fornite dal browser piuttosto che dal motore JavaScript), dovete essere sicuri di testare nei vostri ambienti di destinazione per assicurarvi che l'oggetto fornito dall'host si comporti correttamente. La maggior parte si comporta correttamente (ora), ma è importante testare. La ragione è che la maggior parte dei metodi Array.prototype
che probabilmente vorrete usare si basano sull'oggetto fornito dall'host che dà una risposta onesta all'operazione astratta [[HasProperty]]
. Al momento in cui scrivo, i browser fanno un ottimo lavoro in questo senso, ma la specifica 5.1 ha permesso la possibilità che un oggetto fornito dall'host possa non essere onesto. E' in §8.6.2, diversi paragrafi sotto la grande tabella vicino all'inizio di quella sezione), dove dice:
Gli oggetti host possono implementare questi metodi interni in qualsiasi modo a meno che non sia specificato altrimenti; per esempio, una possibilità è che
[[Get]]
e[[Put]]
per un particolare oggetto host effettivamente recuperano e memorizzano valori di proprietà ma[[HasProperty]]
genera sempre false. (Non ho potuto trovare la verbosità equivalente nella specifica ES2015, ma è destinato ad essere ancora il caso). Anche in questo caso, a partire da questa scrittura i comuni oggetti simili ad array forniti dall'host nei browser moderni [istanzeNodeList
, per esempio] fanno gestire correttamente[[HasProperty]]
, ma è importante testare).
Nota: Questa risposta è irrimediabilmente obsoleta. Per un approccio più moderno, guarda i metodi disponibili su un array. I metodi di interesse potrebbero essere:
Il modo standard per iterare un array in JavaScript è un vanilla for
-loop:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Notate, tuttavia, che questo approccio è buono solo se avete un array denso, e ogni indice è occupato da un elemento. Se l'array è rado, allora potete incorrere in problemi di prestazioni con questo approccio, poiché itererete su molti indici che non esistono realmente nell'array. In questo caso, un ciclo for .. in
potrebbe essere un'idea migliore. Tuttavia, è necessario utilizzare le opportune salvaguardie per assicurarsi che solo le proprietà desiderate dell'array (cioè gli elementi dell'array) siano agite, poiché il ciclo for..in
sarà anche enumerato nei browser legacy, o se le proprietà aggiuntive sono definite come enumerabili
.
In ECMAScript 5 ci sarà un metodo forEach sul prototipo di array, ma non è supportato nei browser legacy. Quindi per essere in grado di usarlo coerentemente devi avere un ambiente che lo supporti (per esempio, Node.js per JavaScript lato server), o usare un "Polyfill". Il Polyfill per questa funzionalità è, comunque, banale e poiché rende il codice più facile da leggere, è un buon polyfill da includere.
Se volete fare un ciclo su un array, usate il ciclo standard in tre parti for
.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Si possono ottenere alcune ottimizzazioni delle prestazioni mettendo in cache myArray.length
o iterando all'indietro.