TL; DR
Mais il y a beaucoup plus à explorer, à lire ...
JavaScript a une sémantique puissante pour parcourir les tableaux et les objets de type tableau. J'ai divisé la réponse en deux parties: les options pour les tableaux authentiques et les options pour les choses qui sont juste comme des tableaux , comme l' arguments
objet, d'autres objets itérables (ES2015 +), les collections DOM, etc.
Je noterai rapidement que vous pouvez utiliser les options ES2015 maintenant , même sur les moteurs ES5, en transposant ES2015 en ES5. Recherchez "ES2015 transpiling" / "ES6 transpiling" pour en savoir plus ...
D'accord, regardons nos options:
Pour les tableaux réels
Vous avez trois options dans ECMAScript 5 ("ES5"), la version la plus largement prise en charge pour le moment, et deux autres ajoutées dans ECMAScript 2015 ("ES2015", "ES6"):
- Utilisation
forEach
et connexes (ES5 +)
- Utilisez une
for
boucle simple
- Utiliser
for-in
correctement
- Utiliser
for-of
(utiliser un itérateur implicitement) (ES2015 +)
- Utiliser explicitement un itérateur (ES2015 +)
Détails:
1. Utilisation forEach
et connexes
Dans tout environnement vaguement moderne (donc pas IE8) où vous avez accès aux Array
fonctionnalités ajoutées par ES5 (directement ou à l'aide de polyfills), vous pouvez utiliser forEach
( spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
accepte une fonction de rappel et, éventuellement, une valeur à utiliser comme this
lors de l'appel de ce rappel (non utilisé ci-dessus). Le rappel est appelé pour chaque entrée du tableau, dans l'ordre, en ignorant les entrées inexistantes dans les tableaux clairsemés. Bien que j'aie utilisé un seul argument ci-dessus, le rappel est appelé avec trois: la valeur de chaque entrée, l'index de cette entrée et une référence au tableau que vous parcourez (au cas où votre fonction ne l'aurait pas déjà à portée de main) ).
À moins que vous ne preniez en charge des navigateurs obsolètes comme IE8 (que NetApps affiche avec un peu plus de 4% de part de marché au moment de la rédaction de cet article en septembre 2016), vous pouvez utiliser avec plaisir forEach
une page Web à usage général sans cale. Si vous devez prendre en charge des navigateurs obsolètes, le calage / polyfilling forEach
se fait facilement (recherchez "shim es5" pour plusieurs options).
forEach
a l'avantage que vous n'avez pas à déclarer les variables d'indexation et de valeur dans la portée contenante, car elles sont fournies en tant qu'arguments à la fonction d'itération, et si bien étendues à cette itération.
Si vous vous inquiétez du coût d'exécution d'un appel de fonction pour chaque entrée de tableau, ne le soyez pas; détails .
De plus, forEach
est la fonction "boucle à travers tous", mais ES5 a défini plusieurs autres fonctions utiles "travaillez à travers le tableau et faites des choses", y compris:
every
(arrête de boucler la première fois que le rappel revient false
ou quelque chose de faux)
some
(arrête de boucler la première fois que le rappel revient true
ou quelque chose de vrai)
filter
(crée un nouveau tableau comprenant des éléments où la fonction de filtre revient true
et en omettant ceux où elle revient false
)
map
(crée un nouveau tableau à partir des valeurs renvoyées par le rappel)
reduce
(construit une valeur en appelant à plusieurs reprises le rappel, en passant les valeurs précédentes; voir la spécification pour les détails; utile pour additionner le contenu d'un tableau et bien d'autres choses)
reduceRight
(comme reduce
, mais fonctionne dans l'ordre décroissant plutôt que croissant)
2. Utilisez une for
boucle simple
Parfois, les anciennes méthodes sont les meilleures:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Si la longueur du tableau ne change pas pendant la boucle, et qu'il est en code sensible aux performances (peu probable), une version légèrement plus compliquée saisissant la longueur à l'avant pourrait être un tout petit peu plus rapide:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Et / ou en comptant à rebours:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Mais avec les moteurs JavaScript modernes, il est rare que vous ayez besoin de retirer ce dernier morceau de jus.
Dans ES2015 et supérieur, vous pouvez rendre vos variables d'index et de valeur locales à la for
boucle:
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"
}
Et lorsque vous faites cela, non seulement value
mais aussi index
est recréé pour chaque itération de boucle, ce qui signifie que les fermetures créées dans le corps de la boucle gardent une référence au index
(et value
) créé pour cette itération spécifique:
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>
Si vous aviez cinq divisions, vous obtiendrez "Index is: 0" si vous avez cliqué sur le premier et "Index is: 4" si vous avez cliqué sur le dernier. Cela ne fonctionne pas si vous utilisez var
au lieu de let
.
3. Utilisez for-in
correctement
Vous aurez des gens qui vous disent d'utiliser for-in
, mais ce n'est pas ce qui for-in
est pour . for-in
parcourt les propriétés énumérables d'un objet , pas les index d'un tableau. La commande n'est pas garantie , pas même en ES2015 (ES6). ES2015 + ne définit un ordre de propriétés de l' objet (via [[OwnPropertyKeys]]
, [[Enumerate]]
et les choses qui les utilisent comme Object.getOwnPropertyKeys
), mais il n'a pas précisé que for-in
suivraient cet ordre; Mais ES2020 l'a fait. (Détails dans cette autre réponse .)
Les seuls cas d'utilisation réels pour for-in
un tableau sont:
- Il est un clairsemés des réseaux avec d' énormes lacunes dans, ou
- Vous utilisez des propriétés non-éléments et vous souhaitez les inclure dans la boucle
En ne regardant que ce premier exemple: vous pouvez utiliser for-in
pour visiter ces éléments de tableau clairsemés si vous utilisez des protections appropriées:
// `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]);
}
}
Notez les trois contrôles:
Que l'objet a sa propre propriété de ce nom (pas celle qu'il hérite de son prototype), et
Que la clé est composée de chiffres décimaux (par exemple, une chaîne de caractères normale et non une notation scientifique), et
Que la valeur de la clé lorsqu'elle est contrainte à un nombre est <= 2 ^ 32 - 2 (qui est 4 294 967 294). D'où vient ce chiffre? Cela fait partie de la définition d'un index de tableau dans la spécification . Les autres nombres (non entiers, nombres négatifs, nombres supérieurs à 2 ^ 32 - 2) ne sont pas des index de tableau. La raison pour laquelle c'est 2 ^ 32 - 2 est que cela fait que la plus grande valeur d'index est inférieure à 2 ^ 32 - 1 , qui est la valeur maximale qu'un tableau length
peut avoir. (Par exemple, la longueur d'un tableau tient dans un entier non signé 32 bits.) (Props à RobG pour avoir souligné dans un commentaire sur mon blog que mon test précédent n'était pas tout à fait correct.)
Vous ne feriez pas cela en code intégré, bien sûr. Vous écririez une fonction utilitaire. Peut-être:
// 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. Utilisation for-of
(utilisez un itérateur implicitement) (ES2015 +)
ES2015 a ajouté des itérateurs à JavaScript. La nouvelle façon d'utiliser les itérateurs est la nouvelle for-of
instruction. Cela ressemble à ceci:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Sous les couvertures, qui obtient un itérateur du tableau et boucle à travers lui, obtenant les valeurs de celui-ci. Cela n'a pas le problème que l'utilisation for-in
a, car il utilise un itérateur défini par l'objet (le tableau), et les tableaux définissent que leurs itérateurs parcourent leurs entrées (et non leurs propriétés). Contrairement for-in
à ES5, l'ordre dans lequel les entrées sont visitées est l'ordre numérique de leurs index.
5. Utilisez explicitement un itérateur (ES2015 +)
Parfois, vous souhaiterez peut-être utiliser explicitement un itérateur . Vous pouvez aussi le faire, même si c'est beaucoup plus maladroit que . Cela ressemble à ceci:for-of
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
L'itérateur est un objet correspondant à la définition de l'itérateur dans la spécification. Sa next
méthode retourne un nouvel objet résultat chaque fois que vous l'appelez. L'objet résultat a une propriété, done
nous indiquant si c'est fait, et une propriété value
avec la valeur de cette itération. ( done
est facultatif s'il le serait false
, value
est facultatif s'il le serait undefined
.)
La signification de value
varie selon l'itérateur; les tableaux prennent en charge (au moins) trois fonctions qui renvoient des itérateurs:
values()
: C'est celui que j'ai utilisé ci-dessus. Elle retourne un itérateur où chacun value
est l'entrée de matrice pour cette itération ( "a"
, "b"
, et "c"
dans l'exemple précédent).
keys()
: Renvoie un itérateur où chacun value
est la clé de cette itération (donc pour notre a
ci-dessus, ce serait "0"
, alors "1"
, alors "2"
).
entries()
: Renvoie un itérateur où chacun value
est un tableau dans le formulaire [key, value]
pour cette itération.
Pour les objets de type tableau
Outre les vrais tableaux, il existe également des objets de type tableau qui ont une length
propriété et des propriétés avec des noms numériques: NodeList
instances, l' arguments
objet, etc. Comment pouvons-nous parcourir leur contenu?
Utilisez l'une des options ci-dessus pour les tableaux
Au moins certaines, et peut-être la plupart ou même toutes, des approches de tableau ci-dessus s'appliquent fréquemment aussi bien aux objets de type tableau:
Utilisation forEach
et connexes (ES5 +)
Les différentes fonctions de Array.prototype
sont "intentionnellement génériques" et peuvent généralement être utilisées sur des objets de type tableau via Function#call
ou Function#apply
. (Voir la mise en garde pour les objets fournis par l'hôte à la fin de cette réponse, mais c'est un problème rare.)
Supposons que vous vouliez utiliser forEach
sur Node
des » childNodes
biens. Vous feriez ceci:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Si vous allez faire beaucoup de choses, vous voudrez peut-être récupérer une copie de la référence de fonction dans une variable pour la réutiliser, par exemple:
// (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`
});
Utilisez une for
boucle simple
De toute évidence, une simple for
boucle s'applique aux objets de type tableau.
Utiliser for-in
correctement
for-in
avec les mêmes garanties qu'avec un tableau devrait également fonctionner avec des objets de type tableau; la mise en garde pour les objets fournis par l'hôte sur # 1 ci-dessus peut s'appliquer.
Utiliser for-of
(utiliser un itérateur implicitement) (ES2015 +)
for-of
utilise l' itérateur fourni par l'objet (le cas échéant). Cela inclut les objets fournis par l'hôte. Par exemple, la spécification de NodeList
from a querySelectorAll
été mise à jour pour prendre en charge l'itération. Les spécifications pour le HTMLCollection
de getElementsByTagName
n'était pas.
Utiliser explicitement un itérateur (ES2015 +)
Voir # 4.
Créer un vrai tableau
D'autres fois, vous souhaiterez peut-être convertir un objet de type tableau en un véritable tableau. Faire cela est étonnamment facile:
Utilisez la slice
méthode des tableaux
Nous pouvons utiliser la slice
méthode des tableaux, qui, comme les autres méthodes mentionnées ci-dessus, est "intentionnellement générique" et peut donc être utilisée avec des objets de type tableau, comme ceci:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Ainsi, par exemple, si nous voulons convertir un NodeList
en un vrai tableau, nous pouvons le faire:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Voir la mise en garde pour les objets fournis par l'hôte ci- dessous. En particulier, notez que cela échouera dans IE8 et versions antérieures, ce qui ne vous permet pas d'utiliser des objets fournis par l'hôte this
comme ça.
Utiliser la syntaxe étendue ( ...
)
Il est également possible d'utiliser la syntaxe de propagation d'ES2015 avec des moteurs JavaScript qui prennent en charge cette fonctionnalité. Comme for-of
, cela utilise l' itérateur fourni par l'objet (voir # 4 dans la section précédente):
var trueArray = [...iterableObject];
Ainsi, par exemple, si nous voulons convertir un NodeList
en un vrai tableau, avec la syntaxe étendue, cela devient assez succinct:
var divs = [...document.querySelectorAll("div")];
Utilisation Array.from
Array.from
(spec) | (MDN) (ES2015 +, mais facilement polyfilled) crée un tableau à partir d'un objet de type tableau, en passant éventuellement les entrées via une fonction de mappage en premier. Donc:
var divs = Array.from(document.querySelectorAll("div"));
Ou si vous vouliez obtenir un tableau des noms de balises des éléments avec une classe donnée, vous utiliseriez la fonction de mappage:
// 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;
});
Avertissement pour les objets fournis par l'hôte
Si tu utilises Array.prototype
fonctions avec des objets de type tableau fournis par l' hôte (listes DOM et autres éléments fournis par le navigateur plutôt que le moteur JavaScript), vous devez vous assurer de tester dans vos environnements cibles pour vous assurer que l'objet fourni par l'hôte se comporte correctement. . La plupart se comportent correctement (maintenant), mais il est important de tester. La raison en est que la plupart des Array.prototype
méthodes que vous voudrez probablement utiliser reposent sur l'objet fourni par l'hôte qui donne une réponse honnête à l' [[HasProperty]]
opération abstraite . Au moment d'écrire ces lignes, les navigateurs font un très bon travail, mais la spécification 5.1 permettait la possibilité qu'un objet fourni par l'hôte ne soit pas honnête. C'est au §8.6.2 , plusieurs paragraphes sous le grand tableau près du début de cette section), où il est écrit:
Les objets hôtes peuvent implémenter ces méthodes internes de quelque manière que ce soit, sauf indication contraire; par exemple, une possibilité est que [[Get]]
, [[Put]]
pour un objet hôte particulier, il récupère et stocke les valeurs des propriétés, mais [[HasProperty]]
génère toujours false .
(Je n'ai pas pu trouver le verbiage équivalent dans la spécification ES2015, mais c'est toujours le cas.) Encore une fois, à ce jour, les objets de type tableau fournis par l'hôte dans les navigateurs modernes [ NodeList
instances, par exemple] faire poignée [[HasProperty]]
correctement, mais il est important de tester.)
forEach
et pas seulementfor
. comme indiqué, en c # c'était un peu différent, et ça m'a dérouté :)