Javascript, objets et prototypes
Par Jean-Christophe le vendredi 12 janvier 2007, 18:12 - Développement - Lien permanent
Voici un petit billet qui présente rapidement le modèle objet de Javascript.
Objets
La majorité des langages objets utilisent la notion de classes. Pour
reprendre une image fréquemment utilisée, la classe est le moule dans lequel on
cuit instancie l'objet. Donc les classes et les objets vivent dans
des mondes séparés.
En javascript c'est différent, il n'existe pas de classes, tout est objet. Voilà une façon de créer un objet en javascript[1] :
js> myobj = {
name : "My Object",
show: function() {
print ("I am " + this.name);
}
}
js> myobj
[object Object]
js> myobj.name
My Object
js> myobj.show()
I am My Object
Voilà myobj est l'objet créé. il possède deux propriétés : name qui est une chaine de caractère et show qui est une fonction ou méthode car elle c'est sous ce nom que la norme ECAM-262 une fonction définie à l'intérieur d'un objet.
L'appel à ces propriétés se fait en utilisant la très classique notation pointée : foo.bar représente la propriété bar de l'objet foo. En fait un objet en javascript peut se résumer à un ensemble d'objet nommés. Et d'ailleurs si on reprend l'exemple précédent :
js> myobj.name My Object js> myobj["name"] My Object
Ce sont 2 façons d'accéder à la propriété name de myobj. Ces deux appels sont sémantiquement identiques.
Constructeurs
Si javascript ne propose pas de classes il définit par contre la notion de constructeur. Un contructeur est une fonction qui crée un nouvel objet et initialise ses propriétés. Un constructeur s'utilise avec le mot clé new. Un rapide exemple :
js> function MyObj(objName) {
this.name = objName;
}
js> myobj = new MyObj("My New Object");
[object Object]
Que se passe-t-il quand on appelle le constructeur MyObj avec new. D'abord un nouvel objet vide est créé ; puis this est initialisé de façon à pointer sur ce nouvel objet. Donc quand on exécute la première instruction de la fonction this pointe sur un objet vide. l'appel this.name = objName; va donc définir la propriété name et la remplir avec l'objet passé en paramètre au constructeur :
js> myobj.name My New Object
Et si on veut ajouter une méthode à notre objet ? Une solution consite à ajouter la méthode comme on l'a fait avec la propriété précédente :
js> function MyObj(objName) {
this.name = objName;
this.show = function() {
print ("I am " + this.name);
};
}
js> myobj = new MyObj("My New Object");
[object Object]
js> myobj.show()
I am My New Object
Effectivement cela fonctionne. Cependant cette solution n'est pas très élégante puisque la méthode va être dupliqué dans tous les objets. Il serait plus intéressant que tous les objets instanciés à partir du constructeur partage le même objet méthode. C'est à cela que servent les prototypes.
Prototypes
Chaque objet possède un pointeur caché sur son un autre objet qu'on appelle son prototype. Le prototype est lui aussi un objet javascript et il possède donc lui aussi un prototype. Ces prototypes forment ce qu'on appelle la chaine de prototypes de l'objet. Quand on essaie d'accéder à la propriété bar de l'objet foo en faisant foo.bar alors l'interpréteur javascript fait la chose suivante : il regarde si foo possède une propriété bar ; si oui alors c'est terminé, si non, il fait la même chose avec le prototype de foo et ainsi de suite jusqu'à avoir trouvé la propriété ou avoir parcouru toute la chaine de prototypes.
Mais comment peut-on définir le prototype d'un objet ? C'est simple : le constructeur possède une propriété prototype qui permet de définir le prototype des objets instanciés avec ce constructeur. Attention, la propriété prototype du constructeur n'est pas le prototype du constructeur.
Je reprends mon exemple :
js> function MyObj(objName) {
this.name = objName;
}
js> MyObj.prototype.show = function() {
print ("I am " + this.name);
};
js> myobj = new MyObj("My New Object");
[object Object]
js> myobj.show()
I am My New Object
Quel avantage à utiliser des prototypes ? Comme les prototypes sont partagés dès qu'on les modifie, les modifications sont immédiatement répercutées. Par exemple :
js> function MyObj(objName) {
this.name = objName;
}
js> MyObj.prototype.show = function() {
print ("I am " + this.name);
};
js> myobj1 = new MyObj("My New Object");
[object Object]
js> myobj1.show()
I am My New Object
js> MyObj.prototype.show = function() {
print ("Je suis " + this.name);
};
js> myobj1.show()
Je suis My New Object
Encore mieux, on peut ajouter des fonctions aux prototypes des objets déjà existants. Par exemple :
js> String.prototype.comment = function() {
return "/* " + this + " */";
}
js> str = new String("This is a comment");
This is a comment
js> str.comment();
/* This is a comment */
Et maintenant toutes les chaines de caractères possèdent la méthode comment. Amusant non ?