Voir le flux RSS

Gugelhupf

[Actualité] [Mini-tuto] Programmation orientée objet en JavaScript

Noter ce billet
par , 13/12/2015 à 23h35 (1402 Affichages)
Auteur : Gokan EKINCI
Date de première publication : 2015-12-12
Date de mise à jour : 2017-06-07
Licence : CC BY-NC-SA

Ce tutoriel s'adresse à tous les développeurs qui ont déjà les bases de la programmation orienté objet et qui souhaitent découvrir la programmation orientée objet avec les prototypes et les classes en JavaScript.
JavaScript est un langage orienté prototype, qui est une forme de forme forme de programmation orienté 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é côte à côte afin de vous montrer les similitudes entre les différents styles d'écritures, n'hésitez donc pas à comparer les numéros de ligne entre les blocs de code. Vous pouvez aussi copier/coller les exemples de codes et les tester avec votre navigateur préféré.


I. 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.

Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 :
    • Pour créer nos properties (cf: « attributs »)... Cette syntaxe rappelle le langage Python où nous déclarons des attributs dans le constructeur.
    • Pour appeler les attributs à l'intérieur de la méthode method1().
  3. Toutes les properties peuvent être utilisés 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 :
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Création d'une fonction constructeur
function Foo(arg1){
   /* *** ATTRIBUTS *** */
   this.attr1 = "One";
   this.attr2 = arg1;
 
   /* *** NE PAS DECLARER VOS METHODES ICI !!! *** */
}
 
/* *** DEFINITION DES METHODES *** */
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) :
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 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/...ludes#Polyfill



II. Héritage par prototype
Créons une nouvelle fonction constructeur ChildFoo qui hérite de Foo :
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Création d'une fonction constructeur enfant
function ChildFoo(arg1, arg2) {
   Foo.call(this, arg1); // Appel du constructeur parent
   this.attr3 = arg2;
}
 
/* *** HERITAGE PAR PROTOTYPE *** */
ChildFoo.prototype = Object.create(Foo.prototype);
 
/* *** DEFINITION DES METHODES 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é.

III. 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)
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
function Foo(arg1) {
 
   /* *** ATTRIBUTS *** */
   this.attr1 = "One";
   this.attr2 = arg1;
 
   /* *** NE PAS DECLARER VOS METHODES ICI !!! *** */
}
 
/* *** DEFINITION DES METHODES *** */
Foo.prototype.method1 = function(param1) {
   console.log(this.attr1 + " " + this.attr2 + " " + param1);
};
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Foo {
   constructor(arg1){
      /* *** ATTRIBUTS *** */
      this.attr1 = "One";
      this.attr2 = arg1;
 
 
   }
 
   /* *** DEFINITION DES METHODES *** */
   method1(param1) {
      console.log(this.attr1 + " " + this.attr2 + " " + param1);
   }
}

Héritage :
Programmation orientée objet avec les prototypes (ES5/ES6) Programmation orientée objet avec les classes (ES6 only)
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
function ChildFoo(arg1, arg2) {
 
   Foo.call(this, arg1); // Appel du constructeur parent
   this.attr3 = arg2;
 
}
 
/* *** HERITAGE PAR PROTOTYPE *** */
ChildFoo.prototype = Object.create(Foo.prototype);
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
class ChildFoo extends Foo {
   constructor(arg1, arg2){
      super(arg1); // Appel du constructeur parent
      this.attr3 = arg2;
   }
}


IV. Création d'objet

JavaScript propose divers styles d'écritures 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)
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
var anonymousInstance = {
 
   a:1, 
   b:2,
   c:function(){
      // Code...
   }
 
};
 
anonymousInstance.c();
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
var anonymousInstance = new function() {
 
   this.a = 1;
   this.b = 2;
   this.c = function(){
      // Code...
   };
 
};
 
anonymousInstance.c();
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
var anonymousInstance = new class {
   constructor(){
      this.a = 1;
      this.b = 2;
      this.c = function(){
         // Code...
      };
   }
};
 
anonymousInstance.c();

Il est même possible de créer un objet, puis de lui ajouter des properties :
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var anonymousInstance = undefined;
 
// Création d'un objet sans properties (5 styles d'écritures é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.
    • 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é 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)
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
Object.getPrototypeOf(anonymousInstance).method2 = function() {
   console.log("method2 !!!");
};
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
anonymousInstance.__proto__.method2 = function() {
   console.log("method2 !!!");
};


V. 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)
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Création d'une fonction constructeur
function Bar(arg1, arg2){
 
 
   // ATTRIBUT PRIVATE
   var privateAttribute = arg1;
 
   // ATTRIBUT PUBLIC
   this.publicAttribute = arg2;
 
   // METHODE PRIVILEGED
   this.privilegedMethod = function() {
      return privateAttribute + this.publicAttribute;
   };
}
 
// ATTRIBUT PUBLIC STATIC
Bar.staticAttribute = "Static attribute !";
 
// METHODE PUBLIC STATIC
Bar.staticMethod = function() {
   return "Static method !";
};
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Création d'une classe
class Bar {
 
   constructor (arg1, arg2) {
      // ATTRIBUT PRIVATE
      // PAS D'ATTRIBUT PRIVATE AVEC LES CLASSES ES6
 
      // ATTRIBUT PUBLIC
      this.publicAttribute = arg2;
 
      // METHODE PRIVILEGED
      this.privilegedMethod = function() {
         return this.publicAttribute;
      };
   }
 
   // ATTRIBUT PUBLIC STATIC
   // PAS D'ATTRIBUT STATIC AVEC LES CLASSES ES6
 
   // METHODE PUBLIC STATIC
   static staticMethod () {
      return "Static method !";
   }
}

Utilisation :
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
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.


Vous êtes arrivé à la fin de ce mini-tutoriel pour créer des structures et instancier des objets en JavaScript ES5 ou ES6. N'hésitez pas à consulter mon profil et mon site (https://gokan-ekinci.appspot.com/) pour plus d'infos.

Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog Viadeo Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog Twitter Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog Google Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog Facebook Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog Digg Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog Delicious Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog MySpace Envoyer le billet « [Mini-tuto] Programmation orientée objet en JavaScript » dans le blog Yahoo

Mis à jour 08/06/2017 à 03h50 par Gugelhupf

Tags: es5, es6, javascript, poo
Catégories
DotNET , Javascript , VB.NET , Développement Web , Programmation

Commentaires

  1. Avatar de SylvainPV
    • |
    • permalink
    Juste pour se mettre d'accord sur les termes, la programmation orientée prototypes et la programmation orientée classes sont deux formes de programmation objet.

    Et l'héritage avec class extends en ES6 est toujours un héritage par prototype, c'est une syntaxe alternative mais le fonctionnement interne est exactement le même. Que ce soit avec la notation ES5 ou ES6, on aura: Foo.prototype.isPrototypeOf(new ChildFoo())
  2. Avatar de Gugelhupf
    • |
    • permalink
    Bonsoir Sylvain, oui les deux types de programmation sont des formes de programmation objet, qui plus est identique dans le sens où la finalité est la même (j'ai pris soin de créer des tableaux avec 2 colonnes pour montrer à quel point les deux types sont similaires), le terme "programmation orientée objet" est généralement utilisé pour les langages dans lesquels on retrouve le mot-clé class mais je ne crois pas avoir vu le terme "programmation orientée classes" pour désigner ces langages (peut-être parce que la majorité des langages objets utilisent le mot-clé class et ne sont pas orienté prototype ?), la preuve en est que lorsque je saisie le terme exacte "programmation orientée classe" ou "programmation orientée classes" le moteur de recherche Google ne m'affiche pas plus de 3 pages.

    Penses-tu que je devrais préciser en conclusion qu'il n'y a pas de réel différence entre les deux types de programmation, mise à part l'écriture ?
  3. Avatar de SylvainPV
    • |
    • permalink
    Je pense qu'il ne faut pas entretenir la confusion objet = classes. Je dis surtout ça en réaction vis à vis de l'intro:
    "Est-ce que JavaScript est orienté objet ?" : La réponse est oui mais seulement depuis la spécification ECMAScript 6.
    JavaScript a toujours été un langage orienté objet depuis ses débuts. J'aime beaucoup la façon dont MDN le présente :
    JavaScript (qui est souvent abrégé en "JS") est un langage de script léger, orienté objet, principalement connu comme le langage de script des pages web. C'est un langage à objets utilisant le concept de prototype, disposant d'un typage faible et dynamique qui permet de programmer suivant plusieurs paradigmes de programmation : fonctionnelle, impérative et orientée objet.
    La POO par prototypes n'est malheureusement pas beaucoup enseignée, pourtant on la retrouve dans de nombreux langages. Il y a des différences bien concrètes entre classes et prototypes, c'est pour ça qu'il ne faut pas se laisser avoir avec le mot-clé "class" de ES6, car il ne s'agit pas de vraies classes mais toujours d'objets prototypés. La syntaxe a été orientée de telle sorte qu'elle paraisse familière aux développeurs Java ou autre langage à classes. Certains trouvent que cela rend les choses plus simples. Personnellement je pense qu'il y a mensonge sur l'étiquette et que cela risque d'être une source de confusion supplémentaire.
  4. Avatar de melka one
    • |
    • permalink
    jour

    sa peut aussi être une opportunité afin de faire découvrir la programmation par prototype.
  5. Avatar de Gugelhupf
    • |
    • permalink
    Bonjour,

    J'ai modifié le passage en question pour éviter les confusions. Pensez-vous que je devrais parler de l'héritage de prototype avec new (alternative à Object.create() ) ou encore de la création d'objet à partir de structure anonyme (new function et syntaxe JSON) ?

    Cordialement,
  6. Avatar de SylvainPV
    • |
    • permalink
    Merci pour la modif, c'est plus correct.

    A toi de voir si tu veux rallonger ton tuto (qui ne sera bientôt plus mini ^^). C'est vrai que new et les constructeurs ont été un premier pas vers la POO par classe. J'ai moi-même écrit un billet sur la comparaison new vs Object.create, avec une opinion bien fixée en faveur de Object.create. Si tu as une opinion différente, ça peut être intéressant d'avoir ton retour pour lancer un débat.

    Citation Envoyé par Gugelhupf
    Bonjour,

    J'ai modifié le passage en question pour éviter les confusions. Pensez-vous que je devrais parler de l'héritage de prototype avec new (alternative à Object.create() ) ou encore de la création d'objet à partir de structure anonyme (new function et syntaxe JSON) ?

    Cordialement,
  7. Avatar de Gugelhupf
    • |
    • permalink
    En effet ce n'est plus un "mini" tuto, j'ai fait le fou en ajoutant une cinquième partie ^^