Programmation orientée objet en JavaScript ES5 et ES6

Gokan EKINCI

Ce tutoriel s'adresse à tous les développeurs qui ont déjà les bases de la programmation orientée objet et qui souhaitent la découvrir avec les prototypes et les classes en JavaScript.

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum.

Commentez

- Titre : Programmation orientée objet en JavaScript ES5 et ES6

- Auteur : Gokan EKINCI

- Parution : 12 décembre 2015

- Mise à jour : 26 août 2017

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Gokan EKINCI. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Programmation orientée prototype

- III. Héritage par prototype

- IV. Programmation orientée objet avec les classes (ES6)

- V. Création d'objet

- VI. Visibilité et éléments statiques

- VII. Conclusion

- VIII. Remerciements

I - Introduction

JavaScript est un langage orienté prototype, qui est une forme de programmation orientée objet, il est donc possible d'instancier des objets et de faire de l'héritage. Je vais dans un premier temps vous présenter comment créer des objets et faire de l'héritage avec les prototypes, puis vous présenter une syntaxe alternative avec les classes introduites dans la spécification ECMAScript 6 (pour information ES6 a été validé et publié le 17 juin 2015).

Ce tutoriel présentera des exemples de code placés côte à côte afin de vous montrer les similitudes entre les différents styles d'écriture, n'hésitez donc pas à comparer les numéros de ligne entre les blocs de code. Vous pouvez aussi copier/coller les exemples de code et les tester avec votre navigateur préféré.

II - Programmation orientée prototype

Dans l'exemple qui va suivre, nous allons créer une structure - à ne pas confondre avec les struct en C - similaire aux classes en utilisant le mot-clé function. Nous utiliserons le terme « fonction constructeur » pour désigner cette structure.

// Création d'une fonction constructeur Foo
function Foo(arg1) {
/* *** ATTRIBUTS *** */
this.attr1 = "One";
this.attr2 = arg1;

// METHODE PRIVILEGED
this.method1 = function(param1) {
console.log(this.attr1 + " " + this.attr2 + " " + param1);
};
}

// Instanciation de l'objet
var myInstance = new Foo("Two");

// Exécuter une méthode
myInstance.method1("Three");

Que pouvons-nous dire à propos de cet exemple de code ?

1. Dans certains langages le mot-clé class est utilisé pour créer… des classes ! Mais pas en JavaScript ES5 et versions antérieures. Ici nous avons créé une fonction constructeur.

2. Nous avons utilisé le mot-clé this :

1. Pour créer nos properties (cf: « attributs »)… Cette syntaxe rappelle le langage Python où nous déclarons des attributs dans le constructeur.

2. Pour appeler les attributs à l'intérieur de la méthode method1().

3. Toutes les properties peuvent être utilisées par votre instance, à l'image des attributs et méthodes des autres langages utilisant la visibilité public.

Vous ne l'avez peut-être pas encore remarqué mais quelque chose ne va pas dans ce code… nous avons déclaré notre méthode à l'intérieur de la fonction constructeur (cette méthode est aussi appelé une méthode privileged). Une nouvelle copie de method1() sera créée à chaque fois que vous instancierez un objet, cela va par conséquent augmenter la consommation de mémoire.

Pour optimiser cela, créons la méthode method1() grâce au mot-clé prototype :

// Création d'une fonction constructeur
function Foo(arg1){
/* *** ATTRIBUTS *** */
this.attr1 = "One";
this.attr2 = arg1;

/* *** NE PAS DÉCLARER VOS MÉTHODES ICI !!! *** */
}

/* *** DÉFINITION DES MÉTHODES *** */
Foo.prototype.method1 = function(param1) {
console.log(this.attr1 + " " + this.attr2 + " " + param1);
};

// Instanciation de l'objet
var myInstance = new Foo("Two");

// Exécuter une méthode
myInstance.method1("Three");

Nous observons ici l'une des puissances de la programmation orientée prototype. JavaScript offre la possibilité d'ajouter des méthodes à tout moment en dehors de la fonction constructeur, cela permet notamment de créer des fonctions polyfills pour émuler des fonctions standard qui ne sont pas implémentés dans les anciens navigateurs. Par exemple la récente fonction Array.prototype.includes()qui sert à vérifier si un élément est bien présent dans un tableau, n'est pas disponible sous la plupart des navigateurs, il est possible de vérifier l'existence d'une property, puis d'ajouter la fonctionnalité si elle n'existe pas (cliquez pour dévoiler le spoiler) :

// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) {

// 1. Let O be ? ToObject(this value).
if (this == null) {
throw new TypeError('"this" is null or not defined');
}

var o = Object(this);

// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;

// 3. If len is 0, return false.
if (len === 0) {
return false;
}

// 4. Let n be ? ToInteger(fromIndex).
//    (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0;

// 5. If n ≥ 0, then
//  a. Let k be n.
// 6. Else n < 0,
//  a. Let k be len + n.
//  b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}

// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
// c. Increase k by 1.
if (sameValueZero(o[k], searchElement)) {
return true;
}
k++;
}

// 8. Return false
return false;
}
});
}

Source : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill

III - Héritage par prototype

Créons une nouvelle fonction constructeur ChildFoo qui hérite de Foo :

// Création d'une fonction constructeur enfant
function ChildFoo(arg1, arg2) {
Foo.call(this, arg1); // Appel du constructeur parent
this.attr3 = arg2;
}

/* *** HÉRITAGE PAR PROTOTYPE *** */
ChildFoo.prototype = Object.create(Foo.prototype);

/* *** DÉFINITION DES MÉTHODES DE LA FONCTION CONSTRUCTEUR ENFANT *** */
ChildFoo.prototype.method2 = function(param1, param2) {
console.log(this.attr1 + " " + this.attr2 + " " + this.attr3 + " " + param1 + " " + param2);
};

// Instanciation de l'objet
var c = new ChildFoo("Two", "Three");

// Exécuter une méthode
c.method1("Three");         // Hérité de Foo
c.method2("Four", "Five");

Que pouvons-nous dire à propos de cet exemple de code ?

1. Nous appelons le constructeur parent avec la fonction call(). Ceci est similaire au super en Java ou base en C#. Le premier paramètre doit être this.

2. Nous réalisons un héritage par prototype avec Object.create(prototype). D'après le MDN, pour Internet Explorer, Object.create() est seulement disponible à partir de IE9.

Si vous souhaitez faire de l'héritage avec les anciens navigateurs, il existe une solution alternative : remplacer ChildFoo.prototype = Object.create(Foo.prototype); par ChildFoo.prototype = new Foo(null);

Cette astuce possède néanmoins un inconvénient : le contenu de votre fonction constructeur parent sera appelé.

IV - Programmation orientée objet avec les classes (ES6)

ES6 apporte un nouveau style d'écriture avec les « classes », plus familier pour certains développeurs, mais qui finalement n'est qu'un sucre syntaxique des prototypes.

Les exemples ci-dessous peuvent ne pas fonctionner si votre navigateur ne supporte pas ES6 mais vous pouvez toujours tester avec un transpiler tel que Babel

Programmation orientée objet avec les prototypes (ES5/ES6)

Programmation orientée objet avec les classes (ES6 only)

1. function Foo(arg1) {

3. /* *** ATTRIBUTS *** */

4. this.attr1 = "One";

5. this.attr2 = arg1;

7. /* *** NE PAS DÉCLARER VOS MÉTHODES ICI !!! *** */

8. }

10. /* *** DÉFINITION DES MÉTHODES *** */

11. Foo.prototype.method1 = function(param1) {

12. console.log(this.attr1 + " " + this.attr2 + " " + param1);

13. };

1. class Foo {

2. constructor(arg1){

3. /* *** ATTRIBUTS *** */

4. this.attr1 = "One";

5. this.attr2 = arg1;

8. }

10. /* *** DÉFINITION DES MÉTHODES *** */

11. method1(param1) {

12. console.log(this.attr1 + " " + this.attr2 + " " + param1);

13. }

14. }

Héritage :

Programmation orientée objet avec les prototypes (ES5/ES6)

Programmation orientée objet avec les classes (ES6 only)

1. function ChildFoo(arg1, arg2) {

3. Foo.call(this, arg1); // Appel du constructeur parent

4. this.attr3 = arg2;

6. }

8. /* *** HÉRITAGE PAR PROTOTYPE *** */

9. ChildFoo.prototype = Object.create(Foo.prototype);

1. class ChildFoo extends Foo {

2. constructor(arg1, arg2){

3. super(arg1); // Appel du constructeur parent

4. this.attr3 = arg2;

5. }

6. }

V - Création d'objet

JavaScript propose divers styles d'écriture pour créer des objets. Le style préféré d'un développeur JS reste celle du literal object, plus court et plus concis.

Style literal object (ES5/ES6)

Style `new function` (ES5/ES6)

Style anonymous class (ES6 only)

1. var anonymousInstance = {

3. a:1,

4. b:2,

5. c:function(){

6. // Code...

7. }

9. };

11. anonymousInstance.c();

1. var anonymousInstance = new function() {

3. this.a = 1;

4. this.b = 2;

5. this.c = function(){

6. // Code...

7. };

9. };

11. anonymousInstance.c();

1. var anonymousInstance = new class {

2. constructor(){

3. this.a = 1;

4. this.b = 2;

5. this.c = function(){

6. // Code...

7. };

8. }

9. };

11. anonymousInstance.c();

Il est même possible de créer un objet, puis de lui ajouter des properties :

var anonymousInstance = undefined;

// Création d'un objet sans properties (5 styles d'écriture équivalents)
anonymousInstance = {};
anonymousInstance = new Object();
anonymousInstance = Object.create(Object.prototype);
anonymousInstance = new function() {};
anonymousInstance = new class {}; // ES6 only

// Création des properties
anonymousInstance.a = 1;
anonymousInstance.b = 2;
anonymousInstance.c = function() {
// Code...
};

Que pouvons-nous dire à propos de ces exemples de code ?

1. Nous avons utilisé différents styles d'écriture pour créer des objets mais ils sont tous équivalents, ils ont tous Object pour prototype.

1. Si vous souhaitez créer un objet qui ne possède aucun prototype, vous pouvez le créer à partir de l'instruction suivante : Object.create(null);

2. Nous n'avons pas utilisé de fonction nommée pour créer ces objets, nous avons créé des objets à usage unique (ex : un message JSON envoyé au serveur). Il n'est pas nécessaire d'avoir recours aux prototypes pour créer des méthodes pour ce type de cas, même s'il est toujours possible d'utiliser les prototypes pour définir des méthodes :

Style `Object.getPrototypeOf` (requiert IE9 ou plus)

Style `__proto__` (requiert un navigateur compatible ES6)

1. Object.getPrototypeOf(anonymousInstance).method2 = function() {

2. console.log("method2 !!!");

3. };

1. anonymousInstance.__proto__.method2 = function() {

2. console.log("method2 !!!");

3. };

VI - Visibilité et éléments statiques

Dans cette partie nous allons voir comment créer des attributs privés (private), des méthodes privileged (vous en avez déjà vu dans la première partie), et des éléments statiques.

Programmation orientée objet avec les prototypes (ES5/ES6)

Programmation orientée objet avec les classes (ES6 only)

1. // Création d'une fonction constructeur

2. function Bar(arg1, arg2){

5. // ATTRIBUT PRIVATE

6. var privateAttribute = arg1;

8. // ATTRIBUT PUBLIC

9. this.publicAttribute = arg2;

11. // MÉTHODE PRIVILEGED

12. this.privilegedMethod = function() {

13. return privateAttribute + this.publicAttribute;

14. };

15. }

17. // ATTRIBUT PUBLIC STATIC

18. Bar.staticAttribute = "Static attribute !";

20. // MÉTHODE PUBLIC STATIC

21. Bar.staticMethod = function() {

22. return "Static method !";

23. };

1. // Création d'une classe

2. class Bar {

4. constructor (arg1, arg2) {

5. // ATTRIBUT PRIVATE

6. // PAS D'ATTRIBUT PRIVATE AVEC LES CLASSES ES6

8. // ATTRIBUT PUBLIC

9. this.publicAttribute = arg2;

11. // MÉTHODE PRIVILEGED

12. this.privilegedMethod = function() {

13. return this.publicAttribute;

14. };

15. }

17. // ATTRIBUT PUBLIC STATIC

18. // PAS D'ATTRIBUT STATIC AVEC LES CLASSES ES6

20. // MÉTHODE PUBLIC STATIC

21. static staticMethod () {

22. return "Static method !";

23. }

24. }

Utilisation :

var o = new Bar(2, 4);
console.log(o.privilegedMethod());
console.log(o.publicAttribute);
console.log(Bar.staticAttribute); // ES5 only
console.log(Bar.staticMethod());

Que pouvons-nous dire à propos de cet exemple de code ?

1. Nous avons utilisé le mot-clé var pour déclarer une variable qui n'est accessible qu'à l'intérieur de notre function. On peut considérer cela comme l'équivalent des attributs/méthodes ayant une visibilité private dans les autres langages. Il n'est pas possible de créer d'attribut private avec les classes ES6.

2. Une méthode privileged est une méthode publique déclarée à l'intérieur d'une fonction constructeur, elle peut contenir des variables (cf : des éléments private) et des properties (cf : des éléments public).

3. Nous n'utilisons pas le mot-clé prototype pour déclarer les éléments statiques, nous créons directement une property à notre function. Comme dans les autres langages, une méthode static ne peut pas accéder aux éléments d'une instance (cf: this). Il n'est pas possible de créer des attributs static avec les classes ES6, peut-être dans une version ultérieure.

VII - Conclusion

La programmation orientée prototype peut paraître déroutante au premier abord, car ce n'est pas un paradigme enseigné dans les écoles où l'on privilégie les langages objets dérivés du C++. Cependant, nous avons vu, au travers de ce tutoriel, les avantages de ce dernier en ajoutant des properties à n'importe quel moment aux fonctions constructeurs existants. Ce tutoriel a aussi été une occasion de vous présenter quelques nouveautés ES6 qui apportent beaucoup de sucre syntaxique au langage, notamment pour inviter les développeurs habitués aux langages dérivés du C++ à faire du JavaScript avec des classes, et peut-être même faciliter leur initiation à des frameworks tels que React et Angular. Si vous souhaitez en apprendre plus sur JavaScript, n'hésitez pas à lire la documentation de son standard ECMAScript.

VIII - Remerciements

Je tiens à remercier jacques_jean pour la relecture orthographique de ce tutoriel.