EDIT : Je vous invite à lire les retours très pertinents et intéressants de @naholyr, @BAKfr et @KoonePoew suite auxquels j’ai modifié les exemples et une partie du contenu de cet article.
Ajoutée sur ECMAScript 5 (ES5) aux côtés de nombreuses autres fonctions tableaux, Array.prototype.forEach
permet de parcourir des tableaux javascript de manière moins verbeuse (plus moderne ?) qu’avec la classique boucle for
.
var array = [1,2,3,4,5]; for (var i = 0 ; i < array.length ; i++) { console.log(array[i]); }
Le bout de code ci-dessus peut alors être écrit de la manière suivante :
var array = [1,2,3,4,5]; array.forEach(function(value) { console.log(value); });
Il faut bien reconnaître que c’est quand même plus sexy ! Je passe volontairement sur les subtilités de l’utilisation d’un callback et sur le contexte de this
dans ce dernier pour m’attarder plutôt sur les quelques exemples de code qui vont suivre.
Je privilégie depuis longtemps l’utilisation du forEach
ES5 en lieu et place du classique for
et j’encourage également à se passer le plus possible des pseudo forEach
implémentés au sein des frameworks tels que AngularJs (angular.forEach
permet par exemple un parcours à la fois de tableaux classiques et de propriétés d’objets ~ tableaux associatifs…) en utilisant au besoin un script tel que es5-shim si le support des vieux navigateurs est obligatoire.
Mais si on applique bêtement cette règle, on en vient parfois à utiliser forEach
en dépit du bon sens…
function isElementVisible(element) { var isVisible = false; element.children.forEach(function(child) { if (child.visible) { isVisible = true; // Ici, on continue alors qu'on a déjà trouvé notre réponse... } }); return isVisible; } var plop = //... if (!isElementVisible(plop)) { // ... }
Beaucoup de développeurs habitués à jQuery et à son jQuery.each() avant de développer en « vrai » Javascript pensent qu’il suffit de faire un return false
dans le callback de l’appel à Array.prototype.forEach
pour breaker le parcours. Or, il n’y a en fait aucun moyen de stopper ce parcours. Partant de ce postulat, le bout de code ci-dessus a perdu en lisibilité, en performance et donc en intérêt.
Comme pointé par @naholyr, @BAKfr et @KoonePoew dans les commentaires, c’est en fait Array.prototype.some
qui répond parfaitement à notre besoin. Comme expliqué sur le site de Mozilla, la méthode some()
teste si certains éléments du tableau passent le test implémenté par la fonction fournie.
Notre code peut alors être très simplement réécrit de cette manière :
function isElementVisible(element) { return element.children.some(function(child) { return child.visible; }); }
Notez que some
s’arrête dès que le callback renvoie true
, ce qui est exactement ce que l’on recherche.
Prenons maintenant l’exemple de la recherche d’un élément par son code :
function findElementByCode(elements, code) { var searchedElement = null; elements.forEach(function(element) { if (element.code === code) { searchedElement = element; } }); return searchedElement; }
Là encore, pas moyen de breaker au sein du forEach()
ce qui peut poser des problèmes sérieux de performance.
Pour améliorer la performance de cette fonction, il est possible de la modifier pour utiliser l’alternative Array.prototype.some qui, elle, autorise bien le break dans le callback :
function findElementByCode(elements, code) { var searchedElement = null; elements.some(function(element) { if (element.code === code) { searchedElement = element; return true; } }); return searchedElement; }
Cette fois, les performances sont de retour mais on perd encore un peu plus en lisibilité. On a ajouté un return
pour breaker (on ne retourne pas la valeur recherchée) et on détourne l’utilisation de la méthode de test some
.
ECMAScript 6 ajoute une fonction find()
sur le prototype de Array qui permettra de réécrire à terme cette recherche de la manière suivante :
function findElementByCode(elements, code) { return elements.find(function(element) { return element.code === code; }); }
Et si finalement, en attendant la sortie et l’adoption d’ES6, la bonne vieille boucle for
n’était tout simplement pas LA solution :
function findElementByCode(elements, code) { for (var i = 0 ; i < elements.length ; i ++) { if (elements[i].code === code) { return elements[i]; } } return null; }
Certains pourraient être tentés de mettre en cache la propriété length dans cette fonction pour des raisons de performances :
function findElementByCode(elements, code) { var elementsLength = elements.length; for (var i = 0 ; i < elementsLength ; i ++) { if (elements[i].code === code) { return elements[i]; } } return null; }
Attention à ce type de micro-optimisation qui n’a plus de sens dans les navigateurs modernes où la mise en cache de length
est faite automatiquement à l’exécution. Comme souvent, il y a certainement des gains de performances bien plus important ailleurs avant qu’il ne soit nécessaire de complexifier le code source pour ce type de pseudo-optimisation (Voici le résultat d’un test intéressant sur jsperf pour vous convaincre : javascript length cache vs no cache).
Finalement, si je devais proposer une bonne pratique ce serait la suivante :
- Utiliser le
forEach
natif ES5 pour les parcours intégraux de tableaux - Rechercher « s’il existe une méthode avec la sémantique que l’on cherche » (cf commentaire de @BAKfr)
- Utiliser
for (var i ; ...)
dans les autres cas