Pour autant, le fait qu'il n'existe pas de classe ne veut pas dire pour autant que la notion d'héritage est inexistante.
Dans cette article, nous allons étudier une méthode basée sur le mécanisme des prototypes. Garder à l'esprit que ce n'est pas la seule méthode possible pour implémenter l'héritage.
La base
Créer de nouveaux objets
Il y a deux façons pour créer un nouvel objet en javascript. La syntaxe littérale ou l'opérateurnew
.
La fonction constructeur
Contrairement à la syntaxe littérale, l'opérateur new nous permet de spécifier une fonction constructeur. La fonction constructeur est une fonction classique qu'on appelle avec l'operateurnew
. Dans une telle fonction, le mot clé this
fait référence à l'objet fraichement créé.
On peut imaginer que, lorsqu'une fonction est utilisé comme "constructeur", javascript réalise quelque chose comme:
L'opérateur
instanceof
nous révèle cependant que ces manières de construire un objet ne produisent pas tout à fait la même chose (nous verrons pourquoi plus loin):
Le prototype
Ajouter des methodes d'instance grâce au prototype
Il existe plusieurs façon d'ajouter des methodes à nos objets. L'une d'entre elles est de définir des fonctions dans l'objet prototype de la fonction constructeur:Le prototype, un objet comme les autres
Il ne faut pas s'imaginer que le prototype est un élément syntaxique du langage qui permet de définir des méthodes d'instances. Le prototype d'une fonction est un objet tout ce qu'il y a de plus commun.D'ailleurs, si vous utilisez le prototype de votre fonction constructeur pour définir des propriétés d'instance, vous risquez d'avoir des surprises:
Le lookup
Le lookup, part 1
L'exemple précédent montre qu'en fait les variables "a" et "b" partage le même objet store. C'est en fait normal. Lorsque vous demandez une propriété d'objet, javascript regarde d'abord si l'objet possède cette propriété. S'il ne la trouve pas, il regarde dans le prototype de la fonction constructeur de l'objet. Cela signifie que si vous modifiez une propriété du prototype d'une fonction, toutes les instances de cette fonction qui ne définissent pas eux-même la propriété seront impactés.Sachez que, lorsque vous définissez une fonction, javascript crée automatiquement un prototype pour votre fonction. Ce prototype contient au moins une propriété
constructor
qui contient à son tour la fonction elle-même. (voir exemple suivant)Comme les objets regarde dans le prototype de leur constructeur en cas de propriété manquante, on en déduit que tout objet possède une propriété
constructor
qui référence la fonction avec laquelle il a été créé.
Le lookup, part 2
Nous savons maintenant que les propriétés d'objets sont d'abord cherché dans l'objet lui-même puis dans le prototype de son constructeur. Mais comment javascript sait qu'un objet est issu de tel ou tel constructeur ?Nous pourrions penser que l'interprétateur en garde la trace, un point c'est tout - et que la manière fait partie de la magie du langage. Heureusement, les implémentations moderne de javascript expose comment est conservé le lien entre un objet et son constructeur.
En deux mots, chaque objet se voit assigner une propriété
__proto__
qui référence le prototype du constructeur. Tout se passe comme si javascript agissait ainsi:
D'ailleurs:
Le lookup, part 3
L'exemple précédent signifie entre autre que l'opérateurnew
est un simple raccourci du langage. Nous pourrions très bien l'implémenté nous même !
Résumons tous notre savoir en deux points: Primo, lorsqu'un objet est créé, on lui fixe une propriété
__proto__
qui référence le prototype du constructeur. Secundo, quand une propriété n'est pas trouvé dans l'objet, javascript regarde si elle existe dans sa propriété __proto__
. La vrai question est la suivante: que se passe-t-il lorsque la propriété n'est pas non plus trouvé dans le
__proto__
? C'est simple, javascript applique le même procédé: il regarde dans sa propriété __proto__
.
On cherche d'abord dans
obj
, puis obj.__proto__
, puis dans dans obj.__proto__.__proto__
, etc. On peut imaginer que cela se passe ainsi:
La chaine des prototypes
Fabriquer une chaine
Nous avons compris comment fonctionne la "chaine" des prototypes. Il nous reste maintenant à exploiter ce que nous savons: comment chainer les proto à notre guise ?La propriété
__proto__
d'un objet est égale au prototype du constructeur, mais quel est le __proto__ du prototype ? Si nous ne le fixons pas nous même, il vaut Object.prototype
:
Le plus simple pour fabriquer une chaine de deux éléments serait de fixer nous même la valeur du proto du prototype :
Mais cette façon de faire n'est pas belle. Si la propriété proto est entourée de deux underscores, c'est pour une raison: nous ne sommes pas sensé y touché. Certaines implémentations de javascript ne le permettent d'ailleurs pas.
Une meilleure façon de faire
Imaginons un constructeur A donné. Nous voudrions écrire un constructeur B qui "hérite" des méthodes de A. Pour cela, nous avons vu qu'il faudrait queB.prototype.__proto__ = A.prototype
. Seulement nous ne voulons pas utiliser explicitement __proto__
.
Il nous faut donc trouver un objet tel que le proto de cet objet soit le prototype de A. Quel objet a cette particularité ? Eh bien, n'importe quelle instance de A !
Notons au passage que nous sommes obligé de remettre la propriété constructor de
B.prototype
. Pourquoi ?
Nous avons vu que javascript crée automatiquement un prototype pour les fonctions, et que ce prototype a une propriété constructor qui référence la fonction. Seulement, dans l'exemple précédent nous écrasons cet objet par défaut avec une instance de A.
Si nous demandons
B.prototype.constructor
, javascript va commencer son lookup, et nous ramener B.prototype.__proto__.constructor
, ou autrement dit A.prototype.constructor
, soit A. C'est pourquoi nous remettons explicitement cette propriété.
Une autre façon de faire
La façon précédente de chainer les prototypes peut poser un problème. Supposons que le constructeur A contienne du code qui s'execute: Ca ne va pas du tout ! quand nous voulons définir l'héritage, nous produisons l'alerte ! Il faudrait qu'à la définition du prototype nous ne fassions rien. Voici une technique qui permet de le faire. Cette technique consiste à passer par un objet intermédiaire: A la lecture de cet exemple on peut se demander pourquoi ne pas faire directement :B.prototype = A.prototype
. On ne peut pas procéder ainsi car alors si on ajoutait des methodes à B on les ajouterait aussi à A. Ce qui n'est pas souhaité !
Par ailleurs, il y a également un inconvénient à cette technique: si des méthodes d'instance sont directement définies dans A, B n'en héritera pas.
Aucun commentaire:
Enregistrer un commentaire