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' argumentsoggetto, 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
forEache relativi (ES5 +)
- Usa un semplice
forciclo
- Utilizzare
for-in correttamente
- Usa
for-of(usa implicitamente un iteratore) (ES2015 +)
- Utilizzare esplicitamente un iteratore (ES2015 +)
Dettagli:
1. Uso forEache relativi
In qualsiasi ambiente vagamente moderno (quindi, non IE8) in cui hai accesso alle Arrayfunzionalità 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);
});
forEachaccetta una funzione di callback e, facoltativamente, un valore da usare come thisquando 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 forEachin 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 falseo qualcosa di falso)
some(interrompe il looping la prima volta che il callback ritorna trueo qualcosa di vero)
filter(crea un nuovo array includendo elementi in cui la funzione filtro ritorna truee 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 forciclo
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 forloop:
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 valuema indexviene 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 varinvece di let.
3. Utilizzare for-in correttamente
Avrai gente che ti dice di usare for-in, ma non for-inè per questo . for-inscorre 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-inseguirebbe quell'ordine; ES2020 invece lo ha fatto. (Dettagli in questa altra risposta .)
Gli unici casi d'uso reali per for-insu 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-inper 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 lengthpuò 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-ofaffermazione. 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-inha, 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-indi 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 nextmetodo restituisce un nuovo oggetto risultato ogni volta che lo chiami. L'oggetto risultato ha una proprietà doneche ci dice se è stato fatto e una proprietà valuecon il valore per quell'iterazione. ( doneè facoltativo se lo fosse false, valueè facoltativo se lo fosse undefined.)
Il significato di valuevaria 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 asopra, 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 lengthproprietà e proprietà con nomi numerici: NodeLististanze, l' argumentsoggetto, 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 forEache relativi (ES5 +)
Le varie funzioni Array.prototypesono "intenzionalmente generiche" e di solito possono essere utilizzate su oggetti simili a array tramite Function#callo 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 forEachsu una Node's childNodesproprietà. 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 forciclo
Ovviamente, un semplice forciclo si applica agli oggetti simili a array.
Utilizzare for-in correttamente
for-incon 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-ofutilizza l' iteratore fornito dall'oggetto (se presente). Ciò include oggetti forniti dall'host. Ad esempio, le specifiche per il NodeListda sono querySelectorAllstate aggiornate per supportare l'iterazione. Le specifiche per il HTMLCollectionda getElementsByTagNamenon 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 slicemetodo degli array
Possiamo usare il slicemetodo 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 NodeListin 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 thisquesto 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 NodeListin 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.prototypefunzioni 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.prototypemetodi 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 [ NodeListcasi, per esempio] fare manico [[HasProperty]]correttamente, ma è importante testarlo.)
forEache non solofor. come detto, in c # era un po 'diverso, e questo mi ha confuso :)