Les prototypes en JavaScript /
un héritage sans class !

29/02/2016

L'héritage par prototype de JavaScript est inspiré du langage Self. Le fonctionnement de l'héritage en JavaScript peut-être parfois un peu déroutant dans cet article / bloc-notes de nombreux exemples tenteront de vous éclairer sur ce sujet.

En JavaScript les string, numbers, les objets.. Ont des propriétés héritées par des objets parents, appelés prototypes.

Par exemple quand on créer un objet des propriétés vont s’ajouter à celui-ci.Des propriétés que nous n’avons pas créées ! Mais si ces propriétés ne sont pas dans l’objet courant alors d’où viennent ces propriétés ??!


var aquarium = { Nemo : {type : "fish", species : "clownfish", length: 3.7} }

Cet objet contient aussi ces méthodes. Vous pouvez voir la liste des méthodes contenues en affichant votre objet dans la console de votre navigateur.


hasOwnProperty: hasOwnProperty() isPrototypeOf: isPrototypeOf() propertyIsEnumerable: propertyIsEnumerable() toLocaleString: toLocaleString() toString: toString() valueOf: valueOf()

En fait ces méthodes sont héritées de l’objet parent.

Il en est de même pour les array qui hérite des propriétés length() pop() push() slice()…

var myArray = ["this","array","inherits"];

myArray.length
// renvoi 3

Pareil pour les functions


function myFunction(){ return "Functions have secret properties too!"; } myFunction.name myFunction.bind();

Non de Zeus Marty ! Mais ça voudrait dire que l’on peut créer un héritage, comme dans d’autres langages ?
Oui !

Comment / fonctionne l'héritage

Lorsque l’on demande une function à un objet, array.. JavaScript va d’abord regarder si cet élément contient la function demandée, si ce n’est pas le cas JavaScript vérifiera dans l’objet parent si cette function existe jusqu’à remonter au premier objet qui à servi de base à l’héritage (le prototype).

Ajouter des propriétés héritables / à un objet d'un type particulier

En suivant notre raisonnement, nous pouvons donc ajouter des functions à un type d’objet particulier que nous pourrons retrouver dans tous les futurs objet de ce « type ». Mais concrètement comment pouvons-nous le faire ? Il nous suffit d’ajouter une propriété héritable dans ce que l’on appelle le prototype !

En fait le prototype est une base, comme une voiture de base sans option, toutes les voitures de ce modèle sont faites à partir de ce modèle de base. Ensuite viennent se rajouter des options (ici des propriétés)

Concrètement, imaginons que nous voulions créer une function qui compterait le nombre de fois ou une lettre précise est utilisée dans une chaine. Nous voulons donc que cette function soit accessible sur tous les objets de type string, de la même manière que length par exemple.

Démo :


var firstName = "Hello World ! I'm Antoine, and you ? "; var lastName = " What's your lastname ?"; String.prototype.countAll = function(letter){ var letterCount = 0; for(var i = 0; i < this.length; i++){ // this fait référence à l'objet courant if(this.charAt(i).toUpperCase() == letter.toUpperCase()){ letterCount++; } } return letterCount; } };

Maintenant nous avons ajouté ce prototype à toutes les string nous pouvons appeler cette function sur toutes les strings.

LastName.countAll('a');
// retourne 3

Réécrire une propriété / issue d'un prototype (héritage)

Comme vous le savez les objet JavaScript ont de base des méthodes hérités. En effet nous pouvons par exemple appeler .valueOf() sur un array, string, object…Mais si nous voulions modifier le comportement de valueof() ?

Exemple de réécriture de valueof()

Pourquoi modifier une fonction native me direz-vous ? valueof() ne permet pas d’afficher le contenu complet d’un objet, un petit exemple :


var Tornado = function(category, affectedAreas, windGust){ this.category = category; this.affectedAreas = affectedAreas; this.windGust = windGust; } var cities = [ ["Kansas", 46310],["Torpeka",123939],["Lenexa", 49398]]; var twister = new Tornado("F5",cities,220); twister.valueOf(); // retourne Tornado {category: "F5", affectedAreas: Array[3], windGust: 220}

En effet affectedAreas retourne Array[3]… Impossible de connaitre le contenu de affectedAreas en utilisant valueOf. Et si nous reécrivions le comportement de valueOf uniquement dans cet objet ? Oui nous pouvons le faire :p

C’est parti, dans cet exemple nous allons re-ecrire valueOf pour que cette fonction retourne le nombre de personnes affectées par la tornade.
(Valeur contenue dans la seconde cellule de chaque sous array de cities).


Tornado.prototype.valueOf = function(){ var sum = 0; for (var i = 0; i < this.affectedAreas.length; i++){ sum += this.affectedAreas[i][1]; } return sum; } // Utilisation twister.valueOf(); // retourne 219647

Il est important de souligné que nous avons réécrit valueOf uniquement dans l’objet Tornado nous n’avons pas touché à la définition de cette function dans l’objet parent.

Exemple de réécriture de toString();

Vous connaissez peut-être la function toString(). Celle-ci permet de convertir un array, un nombre etc… en une string (plutôt logique pour une fonction avec ce nom ^^ )

Exemple d’utilisation de toString();


var x = 4; x.toString(); // renvoi "4" var a = [3,"Hello","World"]; a.toString(); // renvoi "3,Hello,World"

Toujours dans notre exemple de tornade nous voulons que la function toString() sur cet objet renvoie la liste des villes affectées par cette tornade avec d’autres informations plus globale sur la situation dans une belle phrase.


Tornado.prototype.toString = function(){ var list = ""; for (var i = 0; i < this.affectedAreas.length; i++){ list = list +this.affectedAreas[i][0]+', '; } return "This Tornado has bess classified as an "+this.category+", with wind gusts up to"+this.windGust+"mph Affected areas are: "+list+", potentially affecting a population of "+this.valueOf(); } // Pour appeler la function twister.toString();

Trouver le constructeur / de l'objet et son prototype

À force de créer des héritages, nous allons peut être avoir parfois besoin de trouver d’où vient l’objet, par quelle fonction à t’il été créé. Il existe une méthode pour ça. C’est la méthode constructor.
(nous verrons dans un prochain article comment créer un constructor)

Il suffit donc d’appeler


twister.constructor; //Va renvoyer : // function (category, affectedAreas, windGust){ // this.category = category; // this.affectedAreas = affectedAreas; // this.windGust = windGust; // }

Mais si je souhaite connaitre les functions que j’ai précédemment reécrites dans le proptotype de mon objet ?
Et oui cela peut être très utile de savoir si la function va se comporter nativement, ousi celle-ci à un comportement différent (car re-écrite). Nous pouvons appeler de la même façon le constructeur du prototype avec constructor.prototype.

Exemple :


twister.constructor.prototype; //renvoit // Tornado {} // constructor: (category, affectedAreas, windGust) // toString: () // valueOf: () // __proto__: Object

A noté que nous pouvons obtenir exactement le même retour avec une syntaxe légèrement plus courte


twister.__proto__;

Identifier l'origine / d'une propriété

Il est utile de savoir d’où vient la propriété d’un objet. En fait l’objectif est de savoir si l propriété est héritée ou native à l’objet. JavaScript à pensé à tout avec la function hasOwnProperty(); imaginons que nous voulions savoir d’où vient une propriété, nous pourrions créer une function qui chercherait la propriété dans l’objet courant, puis si elle n’est pas là, dans l’objet parent, etc…

Exemple :


Object.prototype.findOwnerOfProperty = function(propName){ var currentObject = this; while (currentObject !== null){ if(currentObject.hasOwnProperty(propName)){ return currentObject; // La propriété est trouvé }else{ currentObject = currentObject.__proto__; // On remonte au parent pour reverifier } } return "No property found !"; // On a rien trouvé :( } twister.findOwnerOfProperty("valueOf"); // retourne // //Tornado {} // constructor: (category, affectedAreas, windGust) // valueOf: () // __proto__: Object

Nous avons réussi à retrouver depuis quel objet valueOf à été en dernier défini.

Voilà concernant l’héritage prototypal en JavaScript, il ne vous manque plus qu’a savoir comment créer des objet avec un constructeur en utilisant la notion d’héritage prototypal sur celui-ci 🙂