Les objets en JavaScript
#1 Qu'est-ce qu'un objet ?
On va prendre un exemple. Supposons que l'on veuille gérer une boutique en ligne d'articles de sport. Tous les articles vont avoir des caractéristiques communes. Ils vont tous avoir un nom de produit, ils vont tous avoir une référence et aussi un prix hors taxes. Ils auront aussi une fonctionnalité commune, car pour tous les articles, on aura besoin de calculer le prix toutes taxes comprises.
- Au niveau du code que l'on va écrire pour gérer cette boutique, on voit bien que pour chaque article, on va avoir besoin d'une variable
produit
pour stocker le nom du produit, d'une autre variablereference
pour stocker la référence et d'une dernière variableprixHT
pour stocker le prix hors taxes. - Ces variables sont liées entre elles, car elles correspondent à un article. Et ces variables ont une valeur spécifique pour chacun de ces articles.
- La première idée, c'est de regrouper ces variables et de les encapsuler dans une "boîte". Cette boîte, on va lui donner un nom et pour ça on va se servir d'une variable. On va l'appeler
article1
. Déjà, je peux le dire, cette boîte, c'est un objet. Ci-dessous, je vous montre sur un schéma ce que sera l'objetarticle1
utilisé pour un "Sac à dos". - Si maintenant, je veux fabriquer un objet pour des "Skis de rando" Eh bien, je vais avoir besoin d'une deuxième boîte. Cette fois, je vais utiliser la variable
article2
et je vais refaire la même chose, c'est-à-dire avoir besoin dans la boîte d'une variable pour le nom du produit, d'une autre pour la référence... Ci-dessous, je vous montre avec un schéma ce que sera l'objetarticle2
. - Deux remarques :
- Vous imaginez facilement que si on fabriquait un objet pour une boutique réelle, il aurait beaucoup plus variables, car il y aurait beaucoup plus de caractéristiques.
- Et je peux ajouter que pour une boutique réelle, on aurait probablement besoin de beaucoup d'articles.
Du coup la deuxième idée va être de fabriquer une "boîte modèle" et de l'utiliser pour construire chacun de nos articles.
Dans notre exemple, cette "boîte modèle", on va l'appeler
Article
. Vous voyez sur le schéma ci-dessous, que les variables dans cette "boîte modèle" ne sont pas initialisées. Cette boîte doit aussi contenir la fonctioncalculerPrixTTC
.Etant donné que l'on se sert de cette "boîte modèle" pour construire les objets. Cette "boîte modèle", on l'a appelé le constructeur. Et maintenant, on ne va plus parler de "boîte modèle", mais on va parler de constructeur.
Maintenant, avant de partir tête baissée dans le code, on va regarder ce que doit faire un constructeur pour construire un nouvel objet, par exemple
article3
. Pour ce nouvel objet, il va allouer de la mémoire, puis il va regarder la liste des variables qu'on lui a donnée, et dans cette mémoire, il va implanter la même liste de variables. Ensuite, il faudra qu'il initialise chacune de ces variables avec la valeur spécifique à ce nouvel objet. - Au niveau vocabulaire: les variables
produit
,reference
,prixHT
s'appellent les propriétés d'un objet et la fonctioncalculerPrixTTC
s'appelle une méthode d'un objet. Ici, il n'y a qu'une seule méthode, mais bien sûr, il pourrait y en avoir plusieurs. - Pour mettre en oeuvre notre constructeur en JavaScript, on a plusieurs solutions. On peut utiliser une classe ou une fonction constructeur et dans la suite du tuto, on va poursuivre l'exemple, en utilisant une classe.
#2 Un point de vocabulaire pour ne pas s'embrouiller
Alors pourquoi pourrait-on s'embrouiller ? Eh bien, parce que l'on peut dire les mêmes choses de plusieurs façons différentes.
- De manière générale, on construit les objets avec un constructeur. C'est ce qu'il faut retenir.
- Si on utilise une classe pour le constructeur, le plus souvent on dit, que l'on instancie la classe. A ce moment-là, on parle d'instance au lieu de parler d'objet. Mais une instance et un objet, c'est strictement la même chose. C'est juste une autre façon de le dire.
- Si on utilise une fonction constructeur, il m'est arrivé de lire que l'on instancie un nouvel objet à partir la fonction constructeur. C'est même une expression, que j'ai trouvé dans la doc MDN.
Ne vous laissez pas embrouiller. Instancier, moi, je le traduis par "prendre un exemplaire". Par exemple, lorsque l'on dit que l'on prend une instance d'une classe, on peut dire que l'on prend un exemplaire de la classe. Lorsqu'on dit que l'on instancie un nouvel objet à partir d'une fonction constructeur, on peut dire que l'on prend un nouvel exemplaire d'un objet à partir d'une fonction constructeur. Ce qui est important, c'est de ne pas perdre de vue qu'il y a d'un côté le constructeur, et de l'autre les objets.
#3 La déclaration d'une classe et la construction des objets
Maintenant, je vais coder le constructeur. Je vais le faire en utilisant les classes JavaScript qui sont disponibles depuis la version ECMAScript 6 soit l'édition de 2015.
Je vais prendre Sublime Text et créer sur le bureau un fichier que je vais appeler Article.html
. Je fais !>script
puis tab
et je donne le titre Article
à la page web.
Maintenant, je fais Open in Browser
dans Sublime de manière à avoir sous les yeux le code et son exécution.
- Qu'est-ce que j'ai fait dans le code ci-dessus ? En premier, j'ai déclaré la classe
Article
. Donc ça, c'est le constructeur. Comprenez bien que c'est une déclaration. C'est comme une déclaration d'une grosse fonction. Ensuite, j'ai appelé la fonction en faisant lenew Article()
. J'ai construit un premier objetarticle1
en utilisant les valeurs spécifiques àarticle1
(* 'Sac à dos'...). Ensuite, j'ai construit un deuxième objetarticle2
en instanciant une nouvelle fois la classe, mais cette fois-ci, en utilisant les valeurs spécifiques àarticle2
(* 'Ski de rando'...). - Pour déclarer la classe
Article
, je dois utiliser le mot réservéclass
et le faire suivre deArticle
. L'usage veut que l'on mette une majuscule àArticle
qui signale que c'est un nom de classe. En fait, cet usage vient des fonctions constructeurs. La majuscule permettant, dans ce cas, de différencier une fonction constructeur d'une fonction normale. On a gardé cet usage avec les classes. - Ensuite, j'ai déclaré une fonction qui doit s'appeler
constructor
(constructeur en français). Donc dans la classe, il y a une fonction qui va faire ce travail de construction, et c'est celle-là. Notez que je n'ai pas eu besoin d'utiliser le mot réservéfunction
. - Cette fonction
constructor
est appelée au moment dunew Article()
. C'est le moteur JavaScript qui l'appelle, ce n'est pas le programmeur. Le programmeur déclenche l'appel deconstructor
, il ne l'appelle pas directement. Et c'est, les arguments qui sont passés ici àArticle()
qui vont être passés à la fonctionconstructor
. - La fonction
constructor
va allouer l'objet en mémoire. Le mot réservéthis
est un pointeur qui va pointer sur la zone mémoire allouée pour ce nouvel objet. Unconsole.log
detypeof this
afficheobject
qui est un pointeur sur un objet. Et unconsole.log
dethis
affiche l'objet qui est en cours de construction. On peut remarquer que leconsole.log
prend un pointeur en argument et qu'il affiche l'objet pointé et non le pointeur. Pour ceux qui ont fait du langage C, ici leconsole.log
déréférence le pointeur. - La fonction
constructor
va initialiser les propriétés du nouvel l'objet avec les valeurs spécifiques du nouvel l'objet. C'est ce qui se fait par exemple ici pour la propriété produitthis.produit = produit
. Pour pouvoir faire ça, il faut que les valeurs du nouvel objet soient en paramètre de la fonctionconstructor
. Je vais avoir besoin d'une valeur pour chaque propriété à initialiser, donc d'un paramètre pour chaque propriété. Et en général, on donne le même nom au paramètre et à la propriété. - Notez bien que les variables
article1
etarticle2
reçoivent chacune la valeur d'un pointeur. C'est une valeur retournée par lenew Article()
. La valeur duthis
. C'est comme ça que les variablesarticle1
etarticle2
récupèrent l'adresse de l'objet en mémoire. - On peut le vérifier avec la console. Je fais un
typeof article1
et je lisobject
. Doncarticle1
est du type pointeur sur objet. - Maintenant, je tape
article1
, je déploie, je peux visualiser les trois propriétésproduit
reference
etprixHT
.
Tout ça, c'est bien, mais où est passée la méthode calculerPrixTTC
? Alors la réponse, je vais la donner dans le chapitre ci-dessous.
#4 Qu'est-ce qu'un prototype ?
- Je clique sur la propriété
__proto__
et je vois apparaître la méthodecalculerPrixTTC()
. - Maintenant, je tape
article2
, puis je déploieArticle
, puis__proto__
et ici aussi, je vois apparaître la méthodecalculerPrixTTC()
. - A ce stade, on pourrait croire que chaque objet a sa propre méthode. Heureusement ce n'est pas le cas. Imaginez qu'il y ait 1000 objets !! Ce serait idiot d'implanter 1000 fois la même méthode en mémoire.
- En fait, la méthode a été placée dans ce que l'on appelle un prototype. Il s'agit d'un objet au sens JavaScript du terme c'est à dire qu'en interne le prototype est implémenté par un objet. Dans notre exemple, c'est le prototype de tous les objets de la classe
Article
. Il y en a qu'un seul et on va le vérifier. - Faites
typeof article1.__proto__
et vous obtenezobject
. - Faites
typeof article2.__proto__
et vous obtenezobject
. - Et si maintenant, vous testez l'égalité de ces deux
__proto__
, eh bien, vous voyez qu'ils sont égaux, c'est-à-dire qu'ils pointent sur le même objet.
Donc, tous les objets construits avec la classe Article
vont partager ce prototype, et il faut remarquer que c'est de cette manière qu'ils vont partager le code de la méthode. Le code de la méthode n'est pas dupliqué.
Cependant, si vous regardez dans le premier __proto__
vous constatez qu'il contient un autre __proto__
qui lui pointe sur le prototype d'un Object
. Ensuite, ça s'arrête. J'ai mis un point d'interrogation sur le schéma, car on va voir à quoi ça correspond dans la suite du tuto.
#5 Comment se servir d'une classe parent pour créer une classe enfant ?
Il y a une deuxième façon d'utiliser la "boîte modèle". Pour créer une nouvelle classe, plutôt que de partir d'une boîte vide, on va récupérer une classe qui existe déjà. Dans la nouvelle classe, on va juste écrire les propriétés et les méthodes que l'on ajoute. La nouvelle classe est appelée classe enfant ou classe dérivée et la classe récupérée est appelée la classe parent. Ce mécanisme qui permet de récupérer des propriétés et des méthodes existantes s'appelle l'héritage en Programmation Orientée Objet (POO).
Il faut bien comprendre que les objets enfants que l'on construira auront les propriétés et méthodes spécifiques à la classe enfant PLUS les propriétés et méthodes de la classe parent. C'est-à-dire les propriétés et méthodes que la classe enfant hérite de la classe parent.
Poursuivons notre exemple. Supposons que l'on veuille mettre des articles en promotion dans notre boutique en ligne. Ces articles ont les mêmes caractéristiques que les autres articles, à savoir qu'ils ont toujours un nom de produit, une référence et un prixHT. Mais en plus, ils ont une propriété supplémentaire qui est la remise que l'on va faire sur le PrixTTC. Ils ont aussi une méthode en plus pour calculer le prix de vente en promotion.
Nous, ce qu'on veut, c'est ne pas tout recommencer. Alors on va se servir de la classe Article
.
Etant donné que nous avons à notre disposition en JavaScript un mécanisme d'héritage, on va récupérer la classe Article
pour construire une nouvelle classe Promo
. Et pour Promo
, on va se contenter d'ajouter une propriété remise
et une méthode calculerPrixPromo()
.
Je vais sauvegarder le fichier Article.html
sous un nouveau nom Promo.html
. Je vais travailler maintenant sur Promo.html
.
- Je déclare la classe
Promo
comme une extension de la classeArticle
. Je dois utiliser le mot réservéextends
. - Maintenant, je dois déclarer un constructeur pour la classe
Promo
. Ce constructeur doit prendre comme paramètres de déclaration l'ensemble des propriétés des deux classes, puisque les propriétés des deux classes doivent être affectées au moment de l'instanciation. Les propriétésproduit
,reference
etprixHT
de la classe parent et la propriétéremise
de la classe dérivée. - Dans ce constructeur, je dois appeler le constructeur de la classe parent en utilisant le mot réservé
super
. Et je passe les paramètres de déclaration du parent. - Ensuite, je dois initialiser la propriété de la classe enfant. Ici, il n'y a que
remise
. J'utilise à nouveauthis
qui pointe ici toujours sur un nouvel objet, mais cette fois-ci un objet de la classePromo
. - Ensuite, je passe au codage de la méthode
calculerPrixPromo
. Pour cela, j'ai besoin d'accéder à la méthodecalculerPrixTTC
de la classe parent et pour y accéder, je dois à nouveau utiliser le mot réservésuper
. Mais cette fois-ci, je dois utiliser la notation pointée. - Ensuite, je peux instancier
Promo
en prenant bien soin de mettre tous les paramètres d'appel.
#6 Héritage par prototype
- Reprenez le fichier
Promo.html
, avec la console examinez l'objetpromo
. - Vous constatez que le prototype de
Promo
semble "contenir" celui d'Article
. En fait, c'est le pointeur__proto__
du prototype depromo
qui pointe vers celui d'Article
.
Ca veut dire qu'un prototype contient un pointeur vers un autre prototype. On a donc une liste chaînée de prototypes qui se termine par le prototype de l'objet Object
.
Pour le vérifier, on va prendre l'objet promo
et on va aller sur son prototype, puis on va descendre sur sa propriété __proto__
. On va comparer cette valeur, à celle de article1.__proto__
. On va constater, que les valeurs sont égales, ce qui prouve le chaînage des prototypes.
Je résume le fonctionnement qui est très simple : si une propriété est demandée, l'interpréteur regarde d'abord dans l'objet. Si elle n'est pas dans l'objet, il la cherche dans le prototype de l'objet. Si elle n'y est pas, il remonte au prototype du parent et ainsi de suite, jusqu'au prototype de Object
. Le prototype de Object
est le prototype de l'objet, qui est en haut de la hiérarchie.
#7 Comment créer un objet littéral ?
C'est une autre façon de procéder pour créer un objet. Dans ce cas, le plus souvent, on commence par créer un objet vide en utilisant les accolades. Puis, on ajoute ou on supprime des propriétés de manière dynamique. JavaScript a cette capacité de nous permettre d'ajouter ou de supprimer des propriétés au fur et à mesure de notre besoin.
Pour ajouter une propriété il suffit d'affecter une nouvelle propriété.
Pour supprimer une propriété il faut utiliser l'opérateur delete
.
- Avec Sublime, faites
CTRL+N
pour créer un nouveau fichier puisCTRL+S
pour le sauvegarder sur le bureau. Vous lui donnez le nom deLitteral.html
. Faites!>script
puistab
. Donnez le titreLitteral
à la page web. - Déclarez un objet vide
article
en utilisant les accolades. - Ajoutez la propriété
produit
en affectantarticle.produit
à'Sac à dos'
. - Ajoutez la propriété
reference
en affectantarticle.reference
à645020
. - Ajouter la propriété
prixHT
en affectantarticle.prixHT
à35
. - Ajouter la propriété
stock
en affectantarticle.stock
à10
. - Supprimez la propriété
reference
avec undelete
surarticle.reference
. - Faites un
console.log()
pour afficher l'objet. - Faites un clic droit dans Sublime, puis choisissez
Open in Browser
. Puis,CTRL+SHIFT+I
pour accéder à la console. - Vérifiez la liste des propriétés.
Vous pouvez aussi utiliser la syntaxe JSON pour créer un objet avec ses propriétés initialisées. Chaque propriété est composée d'un couple nom et valeur. Le nom et la valeur sont séparés par le caractère deux points. Chaque couple nom et valeur est séparé par une virgule.
- Effacez ce qu'il y a dans l'élément
script
deLitteral.html
sauf leconsole.log()
. - Déclarez un objet en utilisant la syntaxe JSON.
- Faites un clic droit dans Sublime puis choisissez
Open in Browser
. PuisCTRL+SHIFT+I
pour accéder à la console. - Vérifiez la liste des propriétés.
#8 Tester si une propriété existe dans un objet
On peut se poser deux questions :
- Est-ce que la propriété existe, que ce soit dans l'objet ou dans sa chaîne de prototype ? C'est l'opérateur
in
qui répond à cette question. Il vous renvoietrue
si la réponse à la question est affirmative. - Est-ce que la propriété existe dans l'objet ? C'est la méthode
hasOwnProperty()
qui répond à cette question. Elle renvoietrue
si la propriété se trouve dans l'objet. Elle renvoiefalse
si la propriété se trouve dans la chaîne de prototype, y compris le prototype de l'objet lui-même.
On va tester ces deux moyens d'abord sur la propriété remise
et sur la méthode calculerPrixPromo()
qui appartiennent à l'objet Promo
, puis sur la propriété valueOf
qui elle appartient à Object
.
#9 Parcourir la liste des propriétés
#10 Passer un objet en paramètre d'appel d'une fonction
Reprenez le fichier Article.html
.
On a vu que article1
est un pointeur. Vous allez passer ce pointeur à une fonction. Le pointeur va être passé par copie comme expliqué dans le tuto sur les fonctions. Mais ce pointeur permettra un accès à l'objet et permettra une modification des propriétés de l'objet depuis la fonction.
Bien sûr, article1
est globale et on peut y accéder dans la fonction modifier
, mais ce n'est pas ce que je veux montrer.