Comment puis-je parcourir en boucle toutes les entrées d'un tableau en utilisant JavaScript ?
Je pensais que c'était quelque chose comme ça :
forEach(instance in theArray)
Où theArray
est mon tableau, mais cela semble être incorrect.
TL;DR
for-in
à moins que vous ne l'utilisiez avec des sauvegardes ou que vous soyez au moins conscient de la raison pour laquelle il pourrait vous mordre.for-of
(ES2015+ seulement),Array#forEach
([spec
][1] | [MDN
][2]) (ou ses parents some
et autres) (ES5+ seulement),for
à l'ancienne,for-in
avec des sauvegardes.
Mais il y a beaucoup de choses à explorer, continuez à lire...JavaScript a une sémantique puissante pour boucler dans les tableaux et les objets de type tableau. J'ai divisé la réponse en deux parties : Les options pour les véritables tableaux, et les options pour les choses qui sont juste des tableaux-semblables, comme l'objet arguments
, d'autres objets itérables (ES2015+), les collections DOM, et ainsi de suite.
Je note rapidement que vous pouvez utiliser les options ES2015 maintenant, même sur les moteurs ES5, en transplantant ES2015 vers ES5. Cherchez "ES2015 transpiling" / "ES6 transpiling" pour en savoir plus...
Bon, regardons nos options :
Vous avez trois options dans [ECMAScript 5][3] ("ES5"), la version la plus largement supportée pour le moment, et deux autres ajoutées dans [ECMAScript 2015][4] ("ES2015", "ES6") :
forEach
et ses dérivés (ES5+)for
.for-in
correctement.for-of
(utiliser un itérateur implicitement) (ES2015+)forEach
et ses dérivésDans tout environnement vaguement moderne (donc, pas IE8) où vous avez accès aux fonctionnalités Array
ajoutées par ES5 (directement ou en utilisant des polyfills), vous pouvez utiliser forEach
([spec
][1] | [MDN
][2]) :
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
accepte une fonction de callback et, optionnellement, une valeur à utiliser comme this
lors de l'appel de cette callback (non utilisée ci-dessus). Le callback est appelé pour chaque entrée du tableau, dans l'ordre, en sautant les entrées inexistantes dans les tableaux épars. Bien que je n'ai utilisé qu'un seul argument ci-dessus, le callback est appelé avec trois : La valeur de chaque entrée, l'index de cette entrée, et une référence au tableau sur lequel vous itérez (au cas où votre fonction ne l'aurait pas déjà sous la main).
À moins que vous ne preniez en charge des navigateurs obsolètes comme IE8 (qui, selon NetApps, ne représente qu'un peu plus de 4 % de part de marché au moment où nous écrivons ces lignes, en septembre 2016), vous pouvez sans problème utiliser forEach
dans une page Web générale sans cale. Si vous avez besoin de supporter des navigateurs obsolètes, le shimming/polyfilling de forEach
est facilement réalisable (recherchez "es5 shim" pour plusieurs options).
forEach
a l'avantage que vous n'avez pas besoin de déclarer les variables d'indexation et de valeur dans le scope contenant, puisqu'elles sont fournies comme arguments à la fonction d'itération, et donc joliment scopées à cette seule itération.
Si vous êtes inquiet du coût d'exécution d'un appel de fonction pour chaque entrée de tableau, ne le soyez pas ; details.
De plus, forEach
est la fonction qui permet de tout parcourir en boucle, mais ES5 a défini plusieurs autres fonctions utiles qui permettent de se frayer un chemin dans le tableau et de faire des choses :
every
][5] (arrête la boucle la première fois que le callback renvoie false
ou quelque chose de faux)some
][6] (arrête la boucle la première fois que le callback retourne true
ou quelque chose de vrai)filter
][7] (crée un nouveau tableau incluant les éléments où la fonction filtre renvoie vrai
et omettant ceux où elle renvoie false
)map
][8] (crée un nouveau tableau à partir des valeurs retournées par le callback)reduce
][9] (construit une valeur en appelant de manière répétée le callback, 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
][10] (comme reduce
, mais fonctionne par ordre décroissant plutôt que croissant)Parfois, les vieilles 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 changera pas pendant la boucle, et que c'est dans un code sensible aux performances (peu probable), une version légèrement plus compliquée saisissant la longueur en amont pourrait être un tiny 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 d'extraire cette dernière partie du jus.
Dans ES2015 et plus, vous pouvez rendre vos variables index et valeur locales à la boucle 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"
}
Et lorsque vous faites cela, non seulement value
mais aussi index
est recréé pour chaque itération de la boucle, ce qui signifie que les fermetures créées dans le corps de la boucle gardent une référence à l'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 divs, vous obtiendriez "Index is : 0" si vous avez cliqué sur la première et "Index is : 4" si vous avez cliqué sur la dernière. Cela ne fonctionne pas si vous utilisez var
au lieu de let
.
for-in
correctement.On vous dira d'utiliser for-in
, mais [ce n'est pas à cela que sert for-in
][11]. for-in
boucle sur les propriétés énumérables d'un objet, pas sur les index d'un tableau. L'ordre n'est pas garanti, pas même dans l'ES2015 (ES6). L'ES2015+ définit un ordre pour les propriétés des objets (via `[[OwnPropertyKeys]], `[[Enumerate]], et les choses qui les utilisent comme [Object.getOwnPropertyKeys
][12]), mais elle ne définit pas que for-in
suivra cet ordre. (Détails dans cette autre réponse.)
Les seuls cas réels d'utilisation de for-in
sur un tableau sont :
for-in
pour visiter ces éléments de tableaux sparés si vous utilisez les 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 vérifications :
longueur
d'un tableau. (Par exemple, la longueur d'un tableau tient dans un entier non signé de 32 bits.) (Bravo à RobG pour avoir signalé dans un commentaire sur mon billet de blog que mon test précédent n'était pas tout à fait correct.)
Vous ne feriez pas cela dans du code en ligne, 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);
});
for-of
(utiliser un itérateur implicitement) (ES2015+)L'ES2015 ajoute les itérateurs à JavaScript. La façon la plus simple d'utiliser les itérateurs est la nouvelle instruction for-of
. Elle ressemble à ceci :
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Sous la couverture, cela obtient un iterator du tableau et boucle à travers lui, en récupérant les valeurs. Cela n'a pas le problème que l'utilisation de 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 itèrent à travers leurs entrées (pas leurs propriétés). Contrairement à for-in
dans ES5, l'ordre dans lequel les entrées sont visitées est l'ordre numérique de leurs index.
Parfois, vous pouvez vouloir utiliser un itérateur explicitement. Vous pouvez le faire aussi, bien que ce soit beaucoup plus compliqué que for-of
. Cela ressemble à ceci :
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'Iterator dans la spécification. Sa méthode next
renvoie un nouvel objet *resultatà chaque fois que vous l'appelez. L'objet résultat a une propriété,
done, qui nous dit si c'est fait, et une propriété
valueavec la valeur pour cette itération. (
doneest facultative si elle serait
false,
valueest facultative si elle serait
undefined). La signification de
value` varie selon l'itérateur ; les tableaux supportent (au moins) trois fonctions qui retournent des itérateurs :
values()
: C'est celle que j'ai utilisée plus haut. Elle retourne un itérateur où chaque valeur
est l'entrée du tableau pour cette itération ("a"
, "b"
, et "c"
dans l'exemple précédent).keys()
: Retourne un itérateur où chaque valeur
est la clé de cette itération (donc pour notre a
ci-dessus, ce serait "0"
, puis "1"
, puis "2"
).entries()
: Retourne un itérateur où chaque value
est un tableau de la forme [key, value]
pour cette itération.En dehors des vrais tableaux, il existe aussi des objets array-like qui ont une propriété length
et des propriétés avec des noms numériques : les instances NodeList
, l'objet arguments
, etc. Comment faire pour boucler sur leur contenu ?
Au moins quelques-unes, et peut-être la plupart ou même toutes les approches de tableau ci-dessus s'appliquent fréquemment aussi bien aux objets de type tableau :
Utiliser forEach
et les fonctions associées (ES5+).
Les diverses 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
][15] ou [Function#apply
][16]. (Voir le Caveat 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 la propriété childNodes
d'un Node
. Vous feriez ceci :
Array.prototype.forEach.call(node.childNodes, function(child) {
// Faites quelque chose avec child
}) ;
Si vous avez l'intention de faire cela souvent, vous pourriez vouloir récupérer une copie de la référence de la fonction dans une variable pour la réutiliser, par exemple :
// (Tout ceci est vraisemblablement dans une fonction de scoping)
var forEach = Array.prototype.forEach ;
// Puis plus tard...
forEach.call(node.childNodes, function(child) {
// Faire quelque chose avec child
}) ;
**Utilisez une simple boucle for
.
Évidemment, une simple boucle for
s'applique aux objets de type tableau.
Utilisez for-in
correctement.
for-in
, avec les mêmes précautions que pour un tableau, devrait également fonctionner avec les objets de type tableau ; la mise en garde concernant les objets fournis par l'hôte au point 1 ci-dessus peut s'appliquer.
Utiliser for-of
(utiliser un itérateur implicitement) (ES2015+)
for-of
utilisera l'itérateur fourni par l'objet (s'il y en a un) ; nous devrons voir comment cela fonctionne avec les différents objets de type tableau, en particulier ceux fournis par l'hôte. Par exemple, la spécification pour le NodeList
de querySelectorAll
a été mise à jour pour supporter l'itération. La spécification pour le HTMLCollection
de getElementsByTagName
ne l'a pas été.
Utiliser un itérateur explicitement (ES2015+). Voir #4, nous devrons voir comment les itérateurs fonctionnent.
Dans d'autres cas, vous pouvez vouloir convertir un objet de type tableau en un vrai tableau. Cela est étonnamment facile à faire :
Utilisez la méthode slice
pour les tableaux.
Nous pouvons utiliser la méthode slice
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) ;
Donc, par exemple, si nous voulons convertir une NodeList
en un vrai tableau, nous pouvons faire ceci :
var divs = Array.prototype.slice.call(document.querySelectorAll("div")) ;
Voir le Caveat pour les objets fournis par l'hôte ci-dessous. En particulier, notez que cela échouera dans IE8 et les versions antérieures, qui ne vous permettent pas d'utiliser les objets fournis par l'hôte comme this
comme cela.
Utiliser la syntaxe étendue (...
)
Il est également possible d'utiliser la [syntaxe d'étalement] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) de la version ES2015 avec les moteurs JavaScript qui prennent en charge cette fonctionnalité :
var trueArray = [...iterableObject] ;
Ainsi, par exemple, si nous voulons convertir une NodeList
en un vrai tableau, avec la syntaxe étendue, cela devient assez succinct :
var divs = [...document.querySelectorAll("div")] ;
Utiliser Array.from
[(spec)][17] | [(MDN)][18]
Array.from
(ES2015+, mais facilement polyfillable) crée un tableau à partir d'un objet de type tableau, en passant éventuellement les entrées par une fonction de mappage au préalable. Ainsi :
var divs = Array.from(document.querySelectorAll("div")) ;
Ou si vous voulez obtenir un tableau des noms de balises des éléments avec une classe donnée, vous utiliserez la fonction de mappage :
// Fonction Flèche (ES2015) :
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName) ;
// Fonction standard (puisque Array.from
peut être shimmé) :
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName ;
}) ;
Si vous utilisez les fonctions Array.prototype
avec des objets de type tableau fournis par l'hôte (listes DOM et autres choses fournies par le navigateur plutôt que par le moteur JavaScript), vous devez vous assurer de tester dans vos environnements cibles pour vérifier 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 méthodes Array.prototype
que vous voudrez probablement utiliser dépendent de la réponse honnête de l'objet fourni par l'hôte à l'opération abstraite [[[HasProperty]]
][19]. À l'heure où nous écrivons ces lignes, les navigateurs font un très bon travail dans ce domaine, mais la spécification 5.1 a prévu la possibilité qu'un objet fourni par l'hôte ne soit pas honnête. C'est dans §8.6.2, plusieurs paragraphes en dessous du grand tableau près du début de cette section), où il est dit :
Les objets hôtes peuvent implémenter ces méthodes internes de n'importe quelle manière sauf indication contraire ; par exemple, une possibilité est que
[[Get]]
et[[Put]]
pour un objet hôte particulier récupère et stocke effectivement les valeurs des propriétés mais que[[HasProperty]]
génère toujours false. (Je n'ai pas trouvé le verbiage équivalent dans la spécification ES2015, mais c'est sûrement toujours le cas). Encore une fois, à ce jour, les objets de type tableau fournis par l'hôte dans les navigateurs modernes [les instancesNodeList
, par exemple] **gèrent correctement[[HasProperty]]
, mais il est important de tester). [1] : https://tc39.github.io/ecma262/#sec-array.prototype.foreach [2] : https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach [3] : http://ecma-international.org/ecma-262/5.1/ [4] : http://www.ecma-international.org/ecma-262/6.0/index.html [5] : https://tc39.github.io/ecma262/#sec-array.prototype.every [6] : https://tc39.github.io/ecma262/#sec-array.prototype.some [7] : https://tc39.github.io/ecma262/#sec-array.prototype.filter [8] : https://tc39.github.io/ecma262/#sec-array.prototype.map [9] : https://tc39.github.io/ecma262/#sec-array.prototype.reduce [10] : https://tc39.github.io/ecma262/#sec-array.prototype.reduceright [11] : http://blog.niftysnippets.org/2010/11/myths-and-realities-of-forin.html [12] : https://tc39.github.io/ecma262/#sec-object.getownpropertynames [14] : http://en.wikipedia.org/wiki/Sparse_array [15] : https://tc39.github.io/ecma262/#sec-function.prototype.call [16] : https://tc39.github.io/ecma262/#sec-function.prototype.apply [17] : https://tc39.github.io/ecma262/#sec-array.from [18] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from [19] : https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
Note : Cette réponse est désespérément obsolète. Pour une approche plus moderne, consultez [les méthodes disponibles sur un tableau][1]. Les méthodes intéressantes sont les suivantes :
La façon standard d'itérer un tableau en [JavaScript][2] est une boucle for
classique :
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Notez, cependant, que cette approche n'est bonne que si vous avez un tableau dense, et que chaque index est occupé par un élément. Si le tableau est clairsemé, alors vous pouvez rencontrer des problèmes de performance avec cette approche, puisque vous itérerez sur beaucoup d'indices qui n'existent pas réellement dans le tableau. Dans ce cas, une boucle for .. in
peut être une meilleure idée. **Cependant, vous devez utiliser les sauvegardes appropriées pour vous assurer que seules les propriétés souhaitées du tableau (c'est-à-dire les éléments du tableau) sont prises en compte, car la boucle for..in
sera également énumérée dans les anciens navigateurs, ou si les propriétés supplémentaires sont définies comme enumerable
.
Dans [ECMAScript 5][3], il y aura une méthode forEach sur le prototype de tableau, mais elle n'est pas supportée par les anciens navigateurs. Pour pouvoir l'utiliser de manière cohérente, vous devez donc soit disposer d'un environnement qui la prend en charge (par exemple, [Node.js][4] pour le JavaScript côté serveur), soit utiliser un "Polyfill". Le Polyfill pour cette fonctionnalité est cependant trivial et comme il rend le code plus facile à lire, c'est un bon Polyfill à inclure.
[1] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array [2] : http://en.wikipedia.org/wiki/JavaScript [3] : https://en.wikipedia.org/wiki/ECMAScript#ECMAScript.2C_5th_Edition [4] : http://en.wikipedia.org/wiki/Node.js
Si vous voulez boucler sur un tableau, utilisez la boucle standard for
en trois parties.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Vous pouvez optimiser les performances en mettant en cache myArray.length
ou en itérant sur le tableau à l'envers.