I. Avant-propos

Les développeurs JavaScript ont été accoutumés à une API très dispersée et incohérente (DOM) depuis un certain temps. Par conséquent, certains des modèles les plus fréquents de JavaScript sont assez bizarres et inutiles lors de la programmation d'une API unifiée et cohérente comme Node. Il peut être facile d'oublier que la spécification ES5 (EcmaScript 5) est entièrement à votre disposition, mais il existe certains modèles standards qui méritent d'être repensés en raison des nouvelles fonctionnalités de ES5.

II. Les objets en ES5

Puisque aucun objet en JavaScript ne peut avoir des clés identiques de même niveau, tous les objets peuvent être considérés comme étant des HashTable. En effet, V8 implémente une fonction de hachagefonction de hachage V8 pour les objets keys. Cette notion importante ne passe pas inaperçue dans le projet ES5 et donc la méthode Object.keys a été créée pour extraire le tableau associatif interne de tout objet et le renvoyer sous forme d'un tableau JavaScript. En termes simples, cela signifie que Object.keys retourne uniquement les clés qui appartiennent à cet objet et non les propriétés dont il pourrait avoir hérité. Il s'agit d'une construction puissante et utile qui peut être utilisée dans Node lors de l'énumération d'un objet.

III. L'ancienne méthode

Vous avez sans doute déjà utilisé ce modèle :

 
Sélectionnez
var key;
for (key in obj) {
 if (obj.hasOwnProperty(key))
   obj[key];
}


C'était le seul moyen de parcourir un objet en ES3 sans remonter la chaîne de l'objet.

IV. Une meilleure méthode

Il y a une meilleure approche en ES5. Étant donné que nous pouvons simplement récupérer les clés d'un objet puis les mettre dans un tableau, nous pouvons boucler sur un objet, mais cela se fait détriment du coût des boucles sur un tableau. Tout d'abord, regardons le code suivant :

 
Sélectionnez
var keys = Object.keys(obj), i, l;

for (i = 0, l = keys.length; i < l; i++)
 obj[keys[i]];


C'est généralement le moyen le plus rapide d'effectuer des boucles sur un objet en ES5 (au moins en V8). Toutefois, cette méthode a quelques inconvénients. Si de nouvelles variables sont nécessaires pour faire des calculs, cette approche commence à se sentir trop verbeuse. Par exemple :

 
Sélectionnez
function calculateAngularDistanceOfObject(obj) {
 if (typeof obj !== 'object') return;
 var keys = Object.keys(obj),
   , EARTH_RADIUS = 3959
   , RADIAN_CONST = Math.PI / 180
   , deltaLat
   , deltLng
   , halfTheSquareChord
   , angularDistanceRad
   , temp
   , a, b, i, l
   ;

 for (i = 0, l = keys.length; i < l; i++) {
   temp = obj[keys[i]];
   a = temp.a;
   b = temp.b;
   deltaLat = a.subLat(b) * RADIAN_CONST;
   deltaLng = a.subLng(b) * RADIAN_CONST;
   halfTheSquareChord = Math.pow(Math.sin(deltaLat / 2), 2) 
                      + Math.pow(Math.sin(deltaLng / 2), 2) * Math.cos(a.lat * RADIAN_CONST) * Math.cos(b.lat * RADIAN_CONST);
   obj[keys[i]].angularDistance = 2 * Math.atan2(Math.sqrt(halfTheSquareChord), Math.sqrt(1 - halfTheSquareChord));
 }
}

V. Une méthode encore meilleure

Dans les situations de ce genre, au lieu d'effectuer des boucles sur le tableau de clés, la méthode native forEach de Array nous permet de créer une nouvelle étendue pour les variables avec lesquelles nous travaillons. Cela permet d'effectuer notre traitement d'une manière plus encapsulée :

 
Sélectionnez
function calculateAngularDistanceOfObject(obj) {
 if (typeof obj !== 'object') return;

 var EARTH_RADIUS = 3959
   , RADIAN_CONST = Math.PI / 180;

 Object.keys(obj).forEach(function(key) {
   var temp = obj[key]
     , a = temp.a
     , b = temp.b
     , deltaLat = a.subLat(b) * RADIAN_CONST
     , deltaLng = a.subLng(b) * RADIAN_CONST;

   halfTheSquareChord = Math.pow(Math.sin(deltaLat / 2), 2) 
                      + Math.pow(Math.sin(deltaLng / 2), 2) * Math.cos(a.lat * RADIAN_CONST) * Math.cos(b.lat * RADIAN_CONST);
   obj[key].angularDistance =  2 * Math.atan2(Math.sqrt(halfTheSquareChord), Math.sqrt(1 - halfTheSquareChord));
 });
}

VI. Analyse comparative

Choisir le bon modèle dépend de l'équilibrage de maintenabilité tout en gardant la performance. Des deux modèles, forEach est généralement considéré comme le plus lisible. Cependant, itérer sur de grands tableaux va en principe être pire avec forEach (ça reste toutefois bien mieux que l'ancienne façon ES3). Il est important d'évaluer correctement les codes avant de prendre une décision.

Une solution couramment utilisée pour Node est le node-benchnode-bench (npm : benchbench) écrit par Isaac Schlueter. Après l'avoir installé, voici une bonne approche :

 
Sélectionnez
var bench = require('bench')
 , obj = { zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9 };

// Il s'agit de simuler l'objet ayant des propriétés non énumérable
Object.defineProperty(obj, 'z', { value: 26, enumerable: false });

exports.compare = {
 'old way': function() {
   for (var name in obj) {
     if (obj.hasOwnProperty(name))
       obj[name];
   }
 },

 'loop array': function() {
   var keys = Object.keys(obj)
     , i
     , l;

   for (i = 0, l = keys.length; i < l; i++)
     obj[keys[i]];
 },

 'foreach loop': function() {
   Object.keys(obj).forEach(function(key) {
     obj[key];
   });
 }
};

// C'est le nombre d'itérations sur chaque test que nous voulons exécuter
bench.COMPARE_COUNT = 8;
bench.runMain();

VII. Remerciements

Cet article a été publié avec l'aimable autorisation de Nathan Sweet. L'article original peut être lu sur le site DailyJSDailyJS : JavaScript for Node Part 1: EnumerationJavaScript for Node Part 1: Enumeration.
Je remercie également Torgar pour sa relecture attentive et assidue.