L'orienté objet en JavaScript, aller plus loin

Publié par Jean-Nicolas Viens le lundi 15 décembre 2014 à 07:08

Maintenant que nous avons solidifié quelques bases en JavaScript, il est temps de créer notre premier objet! Commençons par ce mot-clé souvent mal compris et qui cause bien des maux de tête : le mot-clé this. Ne vous inquiétez pas, le prochain billet contiendra une "cheat sheet" vous permettant de facilement revoir ces concepts!

Mot-clé: this

La façon la plus simple de voir this c'est de se dire qu'il est le propriétaire de la fonction qui est invoquée.

Par exemple :

var cart = function() {
  this.allo = "allo";
}

cart();

console.log(allo); // contient la chaine "allo";

Donc allo est disponible autour de la fonction cart(), car ici this == window (le propriétaire le plus haut niveau possible et par défaut dans le navigateur).

On pourrait donc essayer d'encapsuler ça dans une autre fonction :

(function() {

  var cart = function() {
    this.allo = "allo";
  }

  cart();

  console.log(allo); // contient la chaine "allo";
})(); // IIFE

console.log(allo); // contient la chaine "allo";

console.log(cart); // undefined

Hmm, allo est toujours défini dans window. La portée (scope) est bien respectée : cart n'est pas défini en dehors de la première fonction, mais allo reste défini sur window.

Par contre, si on était en mode strict (avec "use strict";), this serait undefined. Sans le mode strict et puisque this n'est associé à rien le navigateur utilise window par défaut.

Il y a bien sûr moyen d'associer this à une instance et non à window et c'est que nous allons voir.

Mot-clé: new

L'utilité du mot-clé new est d'assigner this à l'objet qu'on est en train de créer. Par convention, les fonctions sur lesquelles on peut exécuter new commencent par une majuscule. Par exemple :

var Cart = function() {
  this.allo = "allo";
  console.log(this); // log : Cart {allo: "allo"} au lieu de Window { ... }
}

var aCart = new Cart();
console.log(aCart.allo); // "allo"
console.log(allo); // undefined.

On dit donc que Cart est un constructeur.

Vie privée 

Par contre, on ne peut toujours pas définir de champ ou méthodes privées avec ce que nous avons. On pourrait utiliser le mot-clé var dans le constructeur, mais aucune des autres méthodes (celles qui sont dans le prototype) ne pourraient voir cette variable. On pourrait tout partager via this, mais alors tout serait public.

En fait, il n'y a pas vraiment de façon d'implémenter un champ privé dans un objet en JavaScript. La seule chose qui change entre les objets est le this. On a donc deux choix :

Idée #1: tout englober dans une fonction (IIFE) supplémentaire afin de créer une nouvelle portée (donc pouvoir utiliser var). Cette idée fonctionne bien, mais tout ce qui est déclaré sans le this et à l'intérieur de la fonction sera partagé entre chaque instance. Dans cet exemple, c'est la variable prive qui est partagée entre les instances a et b :

var Classe = (function() {
  var prive;

  function Classe() {
  }

  Classe.prototype.get = function() { 
    return prive;
  }

  Classe.prototype.set = function(uneValeur) { 
    prive = uneValeur;
  }

  return Classe;
})();
  • >> a = new Classe()
  • Classe {get: function, set: function}
  • >> b = new Classe()
  • Classe {get: function, set: function}
  • >> a.set(4)
  • >> b.get()
  • 4
  • >> b.set(10)
  • >> a.get()
  • 10

Quand on invoque le set() sur a alors le get() de b est affecté. Pas bien pratique pour garder un état. C'est cependant parfait pour définir des fonctions utilitaires à la classe (i.e. méthodes privées qui ne modifient pas l'état directement).

Idée #2: on invente une convention. En JavaScript, tout ce qui commence par un souligné (underscore) est considéré comme privé ou un API interne. C'est la solution qui est la plus souvent utilisée.

Et pour terminer...

Voici la classe initiale :

var Cart;

Cart = (function() {
  function Cart() {
    this.items = [];
  }

  Cart.prototype.addItem = function(item) {
    return this.items.push(item);
  };

  return Cart;

})();

Dans ce code , tout est englobé dans une fonction pour obtenir une portée (scope) privée. Tout ce qui est défini dans cette portée n'en sortira pas, sauf via le return.

Le "punch" de la fin est de retourner le constructeur! Il n'est pas invoqué, juste retourné; c'est ce qui fait que la variable à l'extérieur (le premier Cart) agit comme le constructeur qu'elle contient, mais possède une portée privée.

Attention! Le Cart à la ligne 1 et 3 n'est pas le même qu'à la ligne 4, 8 et 12. On donne simplement le même nom au constructeur qu'à la fonction qui l'entoure par convention.

Vous devriez maintenant pouvoir expliquer chaque ligne de cette classe, à la parenthèse près! Il nous reste par contre quelques sujets supplémentaires qui peuvent s'avérer intéressants à explorer, alors allons-y!

En conclusion

J'espère que ce billet explique bien toutes les techniques requises pour arriver à la solution générée par coffeescript. Ce n'est pas si simple, j'en conviens, alors n'hésitez pas à expérimenter dans un REPL! Vous pouvez également consulter le prochain billet pour un dernier sujet un peu plus poussé, mais surtout une "cheat sheet" récapitulant tout ce que nous avons vu.

Vous pouvez également essayer avec TypeScript si vous préférez. Vous obtiendrez un résultat similaire! Essayez-le ici : http://www.typescriptlang.org/Playground

Voici la version TypeScript :

class Cart {
    items: string[];

    constructor() {
        this.items = [];
    }

    addItem(item: string) {
        this.items.push(item)
    }
}

Si vous avez quelques minutes, n'oubliez pas de remplir ce sondage sur notre formation dédiée aux technologies client (frontend). Vos suggestions aideront à mieux préparer cette formation en fonction de vos besoins!

blog comments powered by Disqus

0 Comments:

Post a comment

Comments have been closed for this post.