Classe et héritage en JavaScript classique

Classe et héritage en JavaScript classique

Même sans le nouveau standard ES6, il est déjà possible en JavaScript de déclarer des classes avec constructeur et d'hériter d'autres classes.

Tout cela se fait en déclarant un objet - donc au départ une fonction en JavaScript - différemment avec une clôture, autrement dit en retournant une fonction au lieu d'une valeur.

Ce modèle de déclaration est offert par TypeScript. Lorsqu'on déclare la classe Voiture avec un attribut vitesse, et une constructeur, il génère le code suivant:

var Voiture = (function () {
  function Voiture(s) {
    this.vitesse = s;
  }
  return Voiture;
})();

La fonction interne Voiture est le constructeur, elle est la valeur de retour et donc est appelée quand crée une instance de Voiture. On vérifie que cela fonctionne en affichant la valeur de l'attribut vitesse:

var mavoiture = new Voiture(150)
document.write(mavoiture.vitesse);

Ajouter des méthodes

Avec ce modèle de déclaration, définir une méthode est un peu plus compliqué. On les déclare comme attribut de l'attribut prototype, que tout objet possède par défaut.
Par exemple, on veut préciser l'autonomie de la voiture avec la méthode setAuto:

var Voiture = (function () {
  function Voiture(s) {
    this.vitesse = s
  }
  Voiture.prototype.setAuto = function (d) {
    this.autonomie = d
  };		
  return Voiture
})();

Comme tout à l'heure, on vérifie que cela fonctionne avec un appel de la méthode suivi par l'affichage de l'attribut.

mavoiture.setAuto(1000)
document.write(mavoiture.autonomie)

Ajouter des attributs

Par principe, les attributs sont déclarés dans le constructeur. Si l'on veut préciser le nom du véhicule par l'attribut nom, le constructeur aura alors cette forme:

function Voiture(s) {
  this.vitesse = s
  this.nom="Modèle x"
}

Il est superflu de déclarer l'attribut au niveau de la classe. On vérifie que l'attribut est bien reconnu:

document.write(mavoiture.nom)

Héritage

Pour réaliser l'héritage, on reprendra le code utilisé par TypeScript qui définit une fonction extends.

var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};

On définit une classe Vehicule dotée d'un attribut couleur dont on veut hériter:

var Vehicule =  (function () {
  function Vehicule(c) {
      this.couleur = c
  }
  return Vehicule;
})();

On doit maintenant ajouter la super classe en même temps que l'on déclare la classe Voiture. Pour cela il faut ajouter l'appel à __extends et dans le constructeur, un appel au constructeur de la super classe. On ajoute aussi le nom de la super classe en fin de déclaration:

var Voiture = (function (_super) {
   __extends(Voiture, _super);

   function Voiture(s) {
     _super.call(this, "red");
     this.vitesse = s;
     this.nom="Modèle x"
   }

   Voiture.prototype.setAuto = function (d) {
     this.autonomie = d;
   };		

   return Voiture;
})(Vehicule);

Le mot _super ne fait pas partie du langage mais est inventé pour transmettre des attributs à la super classe.

Maintenant on va vérifier que l'héritage fonctionne en faisant référence à l'attribut couleur qui appartient à Vehicule et non à Voiture, à partir d'une instance de Voiture.

document.write(mavoiture.couleur)

Multiples instances

Reste à vérifier que les attributs de chaque instance disposent de leur valeurs propres.

On voit dans l'exemple ci-dessous que la vitesse assignée à l'instance v2 n'a pas d'incidence sur celle de v1, comme il se doit.

var v1 = new Voiture(120)
var v2 = new Voiture(240)
document.write("v1.vitesse=" + v1.vitesse)
document.write("v2.vitesse=" + v2.vitesse)

Attribut statique

Pour déclarer un attribut statique, on le déclare simplement dans la classe avec le nom de la classe:

Voiture.conso = 4

Il est alors possible d'accéder à l'attribut directement avec le nom de la classe, mais comme c'est le cas également avec PHP il n'est pas possible d'y accéder par une instance.

Voiture.conso = 5
var v3 = new Voiture(150)
document.write("Voiture.conso=" + Voiture.conso)
document.write("v3.conso=" + v3.conso)

La valeur de conso est indéfinie pour une instance, ce qui est le comportement normal.

Conclusion

Ces pseudo-classes JavaScript peuvent faire tout ce que l'on attend d'une classe, et sont équivalentes aux classes PHP. Elles ont un petit défaut fonctionnel, c'est que l'on perd la possibilité de déclarer un attribut privé. Et aussi un défaut pratique qui est que la déclaration de l'héritage est assez peu lisible.
Si l'on veut utiliser l'héritage de façon intensive, il sera plus simple de recourir à TypeScript pour produire le code JS à notre place à partir d'un code source beaucoup plus simple.
Ceci en attendant qu'ECMAScript 6 soit implémenté sur tous les navigateurs sur lesquels on veut voir faire fonctionner notre application, et qui nous fournira alors des classes et un héritage dans un code simple et plus usuel.

© 21 juillet 2014 Xul.fr