Quantcast
Channel: Publicis Sapient Engineering – Engineering Done Right
Viewing all articles
Browse latest Browse all 1865

Lodash, librairie utilitaire Javascript – alternative à Underscore

$
0
0

La librairie javascript Lodash est une alternative à la librairie utilitaire Underscore, alternative se voulant plus performante, plus consistante et ajoutant quelques fonctionnalités supplémentaires. Les API de Lodash étant pleinement compatibles avec celles de Underscore, nous verrons ci-dessous en quoi l’utilisation de Lodash peut se révéler pertinente.

Retour sur l’historique de Lodash

L’histoire de Lodash démarre avec une pull-request pour Underscore de John-David Dalton dont l’objectif était d’améliorer la consistance des parcours d’objet entre les différents navigateurs.

La pull-request, bien que dans un premier temps acceptée, est finalement rejetée pour plusieurs raisons :

  • des peurs concernant la dégradation des performances ;
  • l’augmentation de la taille totale du fichier ;
  • le bug corrigé est estimé comme étant suffisamment à la marge pour ne pas être traité.

Suite à cela, John-David Dalton décide de créer un fork de Underscore, incluant sa pull-request et mettant le focus sur les performances, la consistance du comportement entre les navigateurs, et sur la modularité. Lodash était né.

Rapide survol de fonctionnalités

Lodash, se veut complètement rétrocompatible avec Underscore, et fourni donc les mêmes fonctionnalités. On citera, de façon non exhaustive :

  var arrayOfPoint = [{x:1,y:2}, {x:3,y:4}, {x:1,y:6}];
  //Des méthodes fonctionnelles classiques
  _.head(arrayOfPoint); // renvoie {x:1,y:2}
  _.tail(arrayOfPoint); // renvoie [{x:3,y:4}, {x:1,y:6}]
  var xs = _.pluck(arrayOfPoint, 'x'); // renvoie [1,3,1]
  _.uniq(xs); // renvoie [1,3]
  _.reduce(xs, function(acc, x) { return acc + x}); // renvoie 5
  _.max(xs); // renvoie 3
  _.groupBy(arrayOfPoint, function(point) {return point.x}); // renvoie {'1':[{x:1,y:2}, {x:1,y:6}], '3':[{x:3,y:4}]}
  _.sortBy(arrayOfPoint, 'x'); // renvoie [{x:1,y:2}, {x:1,y:6}, {x:3,y:4}]
  //Des méthodes utilitaires
  var greeter = function(greeting) {
      return greeting + ' ' + this.name;
  }
  greeter('Hello') // lance une exception car this.name est undefined
  var greeterBound = _.bind(greeter, {name:'World'});
  greeterBound('Hello'); // renvoie Hello World
  _.isArray(arrayOfPoint); // true
  _.isArray({}); // false
  _.keys({x:5,y:8}); // renvoie ['x','y']

Faire le tour de toutes les méthodes serait long et fastidieux, vous pouvez les retrouver dans la documentation.

Les fonctionnalités supplémentaires

Cependant Lodash ne se contente pas d’être un clone de Underscore, la librairie contient aussi certaines fonctionnalités qui lui sont propres. Citons, par exemple :

  • le chainage de fonctions directement depuis l’objet _ :
    _([1, 2, 3, 4]).map(function(val) { return val * 2 }).value() // renvoie [2,4,6,8]
  • le support des chaînes de caractères et de la variable arguments dans les méthodes attendant une collection :
           _.countBy("test") //vaut {e:1, s:1, t:2}   
  • quelques méthodes supplémentaires, tel que cloneDeep, merge ou encore forIn et forOwn qui permettent de controller finement quelles sont les propriétés sur lesquels l’itération se fait.
  • quelques améliorations de méthodes existantes, comme la possibilité de chainer les forEach ou d’en sortir de façon anticipée

Mais la véritable force de Lodash se trouve ailleurs, car l’accent ayant fortement été mis sur les performances, la librairie est dans la très grande majorité des cas plus rapide que Underscore. Nous verrons dans le chapitre suivant comment ce gain de performance a été obtenu.

En plus des performances, Lodash s’assure que les comportements d’itérations sont les mêmes entre tous les navigateurs, par exemple  _.isEmpty({toString:1}) renvoie true sous Internet Explorer 6, 7 et 8 et renvoie false sous les autres navigateurs. Lodash corrige ce problème en renvoyant toujours true. L’équipe Underscore estime que ce n’est pas le rôle de la librairie de corriger ce type de bug lié au navigateur.

Enfin, Lodash permet très facilement de créer un fichier ne contenant que les fonctions que vous utilisez, diminuant ainsi la taille de la librairie. Plusieurs définitions par défaut existent, par exemple le build Backbone construit une version de Lodash ne contenant que les fonctions utilisées par Backbone (backbonejs.org). De nombreuses options sont possibles, mais ce n’est pas le but de l’article que d’en parler en détail, mais on notera cependant au moins le build de Lodash retrocompatible avec Underscore.

Comment Lodash optimise t’il les performances ?

Une question traditionnelle sur Lodash est "Si Lodash est si performant par rapport à Underscore, pourquoi Underscore n’intègre t’il pas les modifications de Lodash ?".
La réponse tient dans la façon dont les itérations sont implémentées dans Lodash.
Pour notre exemple, nous regarderons la méthode map dans Underscore et dans Lodash.

Dans Underscore, la méthode map donne :

_.map = _.collect = function(obj, iterator, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
    each(obj, function(value, index, list) {
      results[results.length] = iterator.call(context, value, index, list);
    });
    return results;
  };

On voit ici qu’elle teste si la méthode map existe en natif sur l’objet, si oui, elle l’utilise, sinon, elle appelle une fonction utilitaire de Underscore qui s’appelle each.

Dans Lodash, la méthode map donne :

function map(collection, callback, thisArg) {
    var index = -1,
        length = collection ? collection.length : 0,
        result = Array(typeof length == 'number' ? length : 0);
    callback = createCallback(callback, thisArg);
    if (isArray(collection)) {
      while (++index < length) {
        result[index] = callback(collection[index], index, collection);
      }
    } else {
      each(collection, function(value, key, collection) {
        result[++index] = callback(value, key, collection);
      });
    }
    return result;
  }

On constatera tout d’abord qu’il n’y a plus de test pour savoir si une méthode native existe ou pas. En effet, les benchmarks tendent à montrer que les méthodes natives ne sont pas nécessairement beaucoup plus rapides que le code "à la main". Ensuite, on constatera que si la collection passée en paramètre est un tableau, on traitera "à la main" celui-ci avec une boucle while. On évite ainsi un appel à une sous-fonction.

Mais l’optimisation ne s’arrête pas là ; si on compare les méthodes each de Underscore et Lodash :

//Version underscore
var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (nativeForEach && obj.forEach === nativeForEach) {
      obj.forEach(iterator, context);
    } else if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === breaker) return;
      }
    } else {
      for (var key in obj) {
        if (_.has(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) return;
        }
      }
    }
  };

Dans Underscore, la méthode each contient toute la logique de parcours, et teste toujours l’existence de méthode native.

//Version Lodash
var each = createIterator(eachIteratorOptions);

La fonction createIterator est une fonction qui à partir d’options liées à l’environnement (navigateur et version, par exemple), va s’appuyer sur un template textuel et "compiler" une fonction d’itération optimisée pour l’environnement courant. Il n’est donc plus nécessaire de tester à chaque fois la présence du forEach natif.
Cependant, cela impose d’avoir une fonction "magique", iteratorTemplate,  déclarée sous forme de chaîne de caractères qui sera évaluée au runtime. Cette approche, qui s’éloigne beaucoup de ce que l’on peut voir dans les librairies Javascript classiques est finalement ce qui améliore le plus les performances de Lodash

Conclusion

Si vous n’êtes pas utilisateur d’Underscore, Lodash vous apportera de nombreuses méthodes utilitaires, qui deviennent rapidement utiles, voir indispensables, lorsque l’on code beaucoup de JavaScript.
Si vous êtes utilisateur d’Underscore, et que vous démarrez un nouveau projet, Lodash est un choix pertinent au sens où les API étant identiques, la courbe d’apprentissage sera très plate, et que vous profiterez ainsi des améliorations de performances.
Cependant, si vous êtes sur un projet existant utilisant Underscore, la migration vers Lodash ne sera finalement intéressante que si vous avez des problèmes de compatibilité entre navigateur ; en effet même si le coût de migration est pratiquement nul, les gains de performance sont suffisamment négligeables pour ne pas prendre le risque d’une régression, les goulots d’étranglement des applications javascript étant rarement situés au niveau de la façon dont on parcours les éléments d’une liste.


Viewing all articles
Browse latest Browse all 1865

Trending Articles