Ingénieur IT, photographe amateur, musicien du dimanche
Le Namespace en JS, pas si trivial…
Le Namespace en JS, pas si trivial…

Le Namespace en JS, pas si trivial…

L’organisation de la plupart des librairies actuelles en JavaScript se fait à l’aide du Design Pattern Namespace permettant d’encapsuler nos fonctionnalités dans un objet afin de ne pas pourrir le global scope. On notera par exemple jQuery et son $  (alias jquery ), d3 et son d3 , Snap.svg et son Snap , etc…
Dernièrement, j’ai voulu moi aussi utiliser ce système pour mieux organiser un projet existant, qui n’implémentait pas la notion d’injection de dépendance et déclarait tout en global.

En plus ça tombe bien, je connais ce Design Pattern… vraiment ?!

J’ai donc commencé naturellement à définir une structure d’objet de ce type :

var myApp = {
  constants : {...},
  models:{...},
  modules : {
   user:{...},
   event:{...}
  }
};

Cette structure décrit rapidement comment sera organisé mon app. Un objet global, contenant les constantes, les modules, fonctions etc…

Le soucis c est que je créé tout une suite d’objets imbriqués vides, et pour l’instant non-utilisés. En plus d’être peu lisible, ceci peut-être particulièrement fastidieux selon la profondeur des namespaces… ce n’est également pas très pratique à l’utilisation, car pour éviter les erreurs, je vais devoir tester si le namespace est déjà créé avant de l utiliser, mais également être sûr que la propriété n’est pas déjà affectée au risque de l écraser…

Un autre problème était l’accès aux modules. Car l’ajout d un namespace allait transformer mon code de ceci

var myUser = new User();

à cela :

var myUser = new myApp.modules.User();

Ce qui n’est ni facilement lisible, ni idéal pour une taille de script optimum.

En bref,ça faisait 10 minutes que j avais commencé et déjà je voyais plus d’aspects négatifs que d’avantages…

Par chance j’avais lu dernièrement le livre JavaScript design pattern – par Addy Osmani, ing. chez Google – sauf qu’arrivé au chapitre des namespaces, j’avais brièvement lu l’intro et étais passé direct au suivant, étant donné que je connaissais déjà le sujet. Comme quoi, comprendre le concept des namespaces est très simple en javascript, mais le mettre en place proprement, c’est une autre histoire.
Après avoir repris le livre, j ai pu trouver une solution a mon premier problème. La fonction suivante, créé par M. Stoyan Stefanov, permet de créer un namespace à partir d une string et de vérifier l’existence de chaque niveau avant de le créer. Ainsi si le niveau existe, on l’utilise plutôt que de le recréer et risquer d’écraser un objet existant.

// Namespace creation method
function setNamespace (ns_string, ns) {
    var parts = ns_string.split('.'),
        parent = ns;
    if (parts[0] === "App") {
        parts = parts.slice(1);
    }
    var pl = parts.length;
    for (var i = 0; i < pl; i++) {
        //create a property if it doesnt exist
        if (typeof parent[parts[i]] === 'undefined') {
            parent[parts[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
};

Cette fonction devrait être facilement accessible par tous nos modules, car les namespaces créés, eux, serviront à organiser notre application proprement.

Maintenant que la fonction est implémentée, il nous reste plus qu’à l’utiliser :

//Create the namespace structure
var nsHelper = setNamespace('App.module.helpers');

Avouez qu’une création avec une string est beaucoup plus lisible qu’une structure d’objet. Et encore plus si l on vient d un langage plus évolué tel que Java, .Net ou autres qui utilisent également cette notation séparée par des points.

Le dernier problème qu’il nous reste à résoudre, est la complexité de l appel aux fonctions et objets encapsulés, qui vont devoir traverser toute la structure d’objets.
Là encore, rien de bien compliqué, nous pouvons simplement faire une affectation de ce type en début de module :

// Use the namespace
var nsHelpers = App.module.hlprs;

Ainsi, l accès au namespace lors de l’utilisation d’un objet, sera beaucoup plus rapide que d’accéder à l’objet imbriqué, mais également plus propre et optimisé que d’accéder à un objet global.

 

Depuis cet expérience, je prend la peine de lire (ou en tout cas de survoler) complètement un chapitre plutôt que de passer à la suite. Comme on dit, la théorie et la pratique sont deux choses parfois bien différentes.

3 commentaires

  1. Félix

    Salut l’auteur, pour ma part, j’utilise la méthode ci-dessous depuis des années pour éviter l’appel à une fonction type « setNamespace « , je n’y ai jamais trouvé de soucis, qu’en penses-tu ?
    Bien à toi.

    /** @namespace (notre NameSpace racine) */
    let NS = {};

    // Puis, imaginons deux composants situé sous NS.component :

    // Composant1.js :
    /** @namespace */
    NS.component = NS.component || {};
    /** @namespace */
    NS.component.monPremier = NS.component.monPremier || {};

    // Composant2.js :
    /** @namespace */
    NS.component = NS.component || {};
    /** @namespace */
    NS.component.monSecond = NS.component.monSecond || {};

    1. Hello,
      le problème de ta solution c’est qu’elle devient très verbeuse dès que le namespace (et donc l’objet) contient plusieurs niveaux.
      Exemple : NS.models.users.etc

      Du coup tu risques d’avoir plusieurs lignes de déclarations alors qu’un appel unique à la fonction ferait le job.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *