Publié par

Il y a 7 ans -

Temps de lecture 15 minutes

Javascript, retour aux bases : constructeur, prototype et héritage

Depuis quelques temps, l’utilisation du javascript se démocratise. De langage permettant de faire clignoter un texte ou défiler un bandeau de pub sur votre site multimania, il est progressivement devenu un outil à part entière de toute application web, jusqu’au point, atteint récemment, d’être auto suffisant, et ainsi de voir émerger un certain nombre de librairies permettant de réaliser la partie interface d’une application (presque) exclusivement en javascript.

Pourtant, on partait de loin ! Très longtemps considéré par la majorité comme un language de bidouilleur tout juste bon à sortir du code inmaintenable, on aura tout entendu sur javascript : depuis « nan mais de toute façon javascript ca marche pas » jusqu’a « nan mais javascript c’est nul, c’est pas orienté objet : y a pas de classe » en passant par  « nan mais moi le prototype j’ai rien pigé, pourquoi ils ont fait ca, sérieux ? », sans oublier le fameux « c’est quoi ce mot clef ‘this’ qui change tout le temps ? c’est vraiment le comportement voulu ? ». Encore maintenant, alors qu’il est devenu un des outils de tous les jours de pas mal de développeurs, ce genre de question perdure, démontrant que le mysticisme autour de javascript n’est toujours pas vraiment levé…

C’est pourquoi je pense qu’il est très important de comprendre les particularités de ce langage lors de son utilisation. Langage dont les concepts sont, de plus, aussi simples que puissants, pour peu que l’on accepte qu’il ne fasse pas tout comme les autres… Cette série d’articles, qui débute avec celui-ci, aura donc cet objectif : la compréhension des concepts inhérents au langage, ainsi que les subtilités et astuces tournant autour. Back to basics.

Dans ce premier article, je vais donc tenter de montrer le plus simplement possible les différentes approches pour faire de l’orienté objet » et des « classes » en javascript (sujet qui, contrairement aux idées reçues, est abordé depuis quelques temps maintenant), jusqu’à arriver sur l’approche prototype, concept apparemment proche du voodoo, que je ne manquerai pas de démystifier. Aller, c’est parti !

La déclaration inline

Parce qu’il faut bien partir de zéro, la première façon de construire un objet en javascript est la déclaration inline. Tout le monde a en effet déjà du croiser une déclaration de ce type :

var myObject = {
 a : 1,
 b : 2,
 sum : function() {
  return this.a + this.b;
 }
};

Ceci est une simple déclaration d’objet. On observe qu’on peut lui spécifier des méthodes comme propriétés, et que ces méthodes peuvent utiliser le mot clef « this ». Il est donc possible de déclarer un objet de cette manière.Cependant, on n’a pas ici de notion de classe ou de constructeur, aucune réutilisation possible. Si l’on veut déclarer un nouvel objet disposant des mêmes méthodes, il faut recommencer la déclaration… Pas très pratique.

Heureusement, des méthodes plus convaincantes existent.

Le constructeur auto déclarant

Car comme peu semblent le savoir, il est en fait très facile de déclarer un « constructeur » d’objet en javascript. En effet, toute fonction peut servir de constructeur : il suffit simplement de l’appeler avec le mot clef ‘new’. Cela peut effectivement être très déroutant au début – une fonction qui est une classe ? einh ? quoi ? – mais une fois le concept accepté, on retrouve une utilisation se rapprochant beaucoup de ce qu’on peut trouver dans les language OO plus « classiques ».

Prenons un exemple tout simple :

var Dog = function() {   // defining Dog constructor
 this.hasTail = true; // defining a property
 this.bark = function() { // defining a method property
  console.log("wouf wouf");
 }
}

var bobby = new Dog(); // instanciating the constructor

bobby.hasTail; // returns true
bobby.bark();  // print "wouf wouf" to the console

Et voila, voici une classe toute bête. On observe quand même que pour définir une propriété des futures instances, il faut utiliser le mot clef this. En effet ‘var’ ne suffit pas. C’est d’ailleurs une particularité intéressante dont on peut se servir pour déclarer des variables privées de l’instance : 

var Dog = function() {
 var shout = "wouf wouf"; // defining a local var.
 this.bark = function() {
  console.log(shout); // local scope access the 'shout' variable
 }
}

var bobby = new Dog();

bobby.bark();  // print "wouf wouf" to the console
bobby.shout;   // undefined , not attached to the instance

Comme pour une fonction classique, il est également possible de passer des paramètres à un constructeur :

var Dog = function(breed) {
 this.breed = breed;
}

var bobby = new Dog("terrier");

bobby.breed;  // returns "terrier"

Cette méthode fonctionne bien. Cependant un problème de taille apparaît : comme la déclaration des méthodes se fait lors de l’instanciation, les instances ne partageant pas leurs méthodes.

En d’autres termes :

var Dog = function() {
 this.bark = function() {
  console.log(shout);
 }
}

var bobby = new Dog();
var felix = new Dog();

(boddy.bark == felix.bark); // returns false. They both declared their own method. 

Si ce n’est pas très gênant sur une faible volumétrie d’instances, cela le devient cependant vite sur une application ‘full’ javascript avec des centaines de milliers d’objets, voir plus. De plus, il n’est pas possible de faire de l’héritage de classe avec cette approche (nous reviendrons sur ce point), ce qui est encore plus problématique en vu de notre objectif…

C’est ici que l’utilisation du prototype entre en jeu.

Constructeur et prototype

Alors, qu’est-ce qu’un prototype ? Et bien dit simplement, c’est une liste de propriétés, attaché à un constructeur, qui va servir de « fallback » lorsque l’on cherche à accéder à une propriété manquante d’une instance dudit constructeur. En effet, lorsque l’on cherche à accéder à une propriété sur une instance, on (comprendre : le langage)  va d’abord regarder si l’instance contient cette propriété, et dans le cas contraire, va ensuite vérifier si le prototype de son constructeur la contient.

Si vous ne saviez pas ce qu’était un prototype avant le paragraphe précédent, vous en êtes probablement au même point ici. C’est normal. Ici un exemple sera plus efficace que toutes les définitions du monde.

Toute fonction déclaré possède nativement une propriété « prototype » initialement vide :

var Dog = function() {}; // declaring constructor
Dog.prototype;     // prints "{}" : prototype exists and is empty

Une propriété rajouté sur le prototype du constructeur devient disponible sur les instances :

Dog.prototype.bark = function() { // defining a method on the Dog prototype
 console.log("wouf wouf");
};

var bobby = new Dog();
bobby.bark(); // using the prototype declaration - prints "wouf wouf" to the console

var felix = new Dog();
(bobby.bark == felix.bark); // instances share same reference - returns true;
bobby.bark = Dog.prototype.bark // which is the prototype property - returns true;

Voila. C’est tout pour le principe de base, qui est au final aussi simpliste qu’inhabituel : on défini un constructeur, on lui rattache des propriétés (qui peuvent être des fonctions) via son prototype, on instancie le constructeur, l’instances dispose des propriétés définie sur le prototype de son constructeur. Voila, c’est tout. Simple non ?

Un peu plus sur l’utilisation du prototype

Il y a plusieurs subtilités à connaitre quant à l’utilisation des prototypes :

On peut surcharger sur une instance une propriété définie sur son prototype. Cependant cela ne surcharge que sa propre référence, et ne touche donc pas à la déclaration du prototype. Si l’on reprend la classe Dog telle que définie ci dessus : 

var bobby = new Dog();
var felix = new Dog();

bobby.bark = function() { console.log("beeeee !") }; // setting the bark property on dogA

bobby.bark(); // using the instance function, prints "beeee !" to the console

felix.bark(); // still using the prototype function, prints "wouf wouf" to the console. 

Une modification du prototype est immédiate sur les instances déjà existantes et les futures. En effet le prototype est une référence des instances, et le « fallback » se fait au moment de l’accès à la propriété.

var bobby = new Dog();

bobby.bark(); // prints "wouf wouf" to the console

Dog.prototype.bark = function() { console.log("meow meow"); } // overrides the prototype bark method

bobby.bark(); // now prints "meow meow" to the console.  

Nous arrivons à une particularité beaucoup plus intéressante : l’extension. En effet, tout comme une méthode modifiée sur le prototype est immédiatement active, un rajout l’est aussi :

var bobby = new Dog();

bobby.sleep(); // Raise a TypeError : has no method sleep

Dog.prototype.sleep = function() { console.log("Zzz Zzz"); }

bobby.sleep(); // now prints "Zzz Zzz" to the console. 

La classe d’une instance (enfin, techniquement, le constructeur) est accessible via la propriété constructor. Cela permet également d’accéder à son prototype (même s’il est très rare d’y voir une utilité) :

bobby.constructor === Dog; // bobby constructor is Dog, returns true

bobby.constructor.prototype.bark // direct access to the prototype

Et le mot clef instanceof de javascript fonctionne bien avec les constructeurs :

bobby instanceof Dog; // bobby is an instance of Dog, returns true

Héritage, première approche

Nous sommes donc capable de déclarer un constructeur et un prototype, ce qui se rapproche énormément d’une déclaration de classe dans des langages OO plus classiques. Cependant, il nous manque une particularité souvent indispensable de l’orienté objet : l’héritage entre classes.

En javascript, il est possible de faire de l’héritage simplement en étendant un prototype à partir d’un autre. C’est par ce moyen que nous allons parvenir à faire notre première implémentation de l’héritage en javascript. Démonstration:

var Dog = function() {}; // defining Dog constructor and prototype
Dog.prototype.bark = function() { console.log("wouf wouf"); };

var Doberman = function() {}; // defining Doberman constructor
for(key in Dog.prototype) {   // copying all Dog prototype property into the Doberman property
 Doberman.prototype[key] = Dog.prototype[key];
}
Doberman.growl = function() { console.log("aouww"); }; // then adding some Doberman specific functions

var rufus = new Doberman();

rufus.bark();  // using the Dog bark method, prints "wouf wouf";
rufus.growl(); // using the Doberman growl method, prints "aouww";

Nous avons donc ici bel et bien deux déclarations de prototype, dont l’un étend les propriétés de l’autre. C’est la manière la plus simple de rendre disponible des méthodes dans différents prototypes, et peut être suffisant pour les besoins d’héritages les plus simples.

Attention cependant à ne surtout pas faire cela : 

var Dog = function() {};
Dog.prototype.bark = function() { console.log("wouf wouf"); };

var Doberman = function() {};
Doberman.prototype = Dog.prototype; // warning : never do that : will share the same prototype !
Doberman.growl = function() { console.log("aouww"); };

Car dans ce cas la, Dog et Doberman partageraient le même prototype. Tout rajout ou modification sur l’un sera aussi effectif sur l’autre. Ce qui déclenche en général des effets de bord assez magnifiques et qui peuvent être compliqués à identifier.

Les limites de cette approche

On arrive ici dans les parties les plus techniques. Pour comprendre le besoin, il faut tout d’abord pointer du doigt deux problèmes de notre approche précédente qui, au final, ne fait que copier, à un instant T, les propriétés d’un prototype vers un autre.

Premier problème : instanceof ne fonctionne pas correctement :

rufus instanceof Doberman; // returns true
rufus instanceof Dog; // oh no, return false, ack ! 

En effet, nous avons simplement étendu le prototype du second avec des méthodes du premier. Les constructeurs de Dog et Doberman n’ont rien en commun.

Le second problème est qu’avec notre technique actuelle, nous avons perdu une partie de la magie du prototype :

var Dog = function() {}; // defining Dog
Dog.prototype.bark = function() { console.log("wouf wouf"); };

var Doberman = function() {}; // defining Doberman
for(key in Dog.prototype) {
 Doberman.prototype[key] = Dog.prototype[key];
}
Doberman.growl = function() { console.log("aouww"); };

var bobby = new Dog();
var rufus = new Doberman();

Dog.prototype.lick = function() { console.log("it tickles !") }; // adding a new method to the Dog's proto

bobby.lick(); // Dog instance can use this new method, prints "it tickles !"
rufus.lick(); // Doberman instance can't ! TypeError : Object [object] has no method 'tickle'

Et oui ! Comme nous avons juste, à un moment donné, copié les propriétés d’un proto vers l’autres… les modifications apportées après cette copie n’affectent pas ce dernier.

Le chainage de prototypes

Heureusement, il est possible de palier ces problèmes en chaînant les prototypes. Et oui, un peu comme dans Inception : a prototype in a prototype, we have to go deeper !

Expliqué simplement, nous allons faire en sorte que le prototype de Doberman ai lui même comme prototype Dog ! Ainsi, les instances de doberman regarderont dans le prototype de Doberman qui lui même regardera dans celui de Dog (même ce n’est pas exactement ca, mais nous allons le voir juste en dessous)

Alors, comment fait-on ça ? comme ceci :

var Dog = function() {}; // defining Dog constructor
Dog.prototype.bark = function() { console.log("wouf wouf"); }; // adding methods to Dog proto

var Doberman = function() {}; // defining Doberman constructor

var Surrogate = function() { // defining doberman surrogate constructor.
    this.constructor = Doberman;
};
Surrogate.prototype = Dog.prototype;  // setting Dog as the Surrogate prototype
Doberman.prototype = new Surrogate(); // then setting a surrogate instance as Doberman prototype

var rufus = new Doberman(); // here we go !

Comme on le voit en dessus, il y a en effet une subtilité : un prototype ne peut lui même avoir de prototype. Par contre, un constructeur peut avoir comme prototype une instance d’un autre constructeur disposant lui même d’un prototype ! Ce qui revient au comportement voulu, de manière un peu détournée.

Et c’est ce qu’on fait ici : On crée un constructeur intermédiaire « Surrogate » dont le prototype est Dog. puis on instancie ce constructeur, ce qui nous donne une instance ayant accès aux méthodes du prototype Dog. Et on fait de cette instance le prototype de notre constructeur Doberman. Et voila ! On arrive ici à avoir un héritage de ‘classe’ très proche de celui qu’on pourrait trouver en java par exemple :

var rufus = new Doberman();
Dog.prototype.lick = function() { console.log("it tickle") }; // adding new method to Dog prototype
rufus.lick(); // it works ! prints "it tickle" // rufus instance has access to the new method !

rufus instanceof Doberman; // still returns true
rufus instanceof Dog; // yippie ! now returns true !

Et de plus, notre autre problème est au passage corrigé : instanceof se comporte maintenant correctement. En effet, en interne, ce mot clef se base sur la chaine de prototype pour savoir si x est instance de X !

Situation réelle

Je sais ce que vous vous dites : oui mais…. si je dois faire ca à chaque fois que je veux faire un héritage de classe, ça va être un peu compliqué non ? Eh bien, c’est sur que dans l’état actuel des choses, c’est du code qui n’est pas vraiment réutilisable. Si l’on devait refaire le tour de passe passe du surrogate à chaque fois, cela serait un peu compliqué. Heureusement, ce code est facilement factorisable pour permettre une réutilisation :

var extendClass = function(child, parent) {
    var Surrogate = function() {};
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate();
};

var Dog = function() {};
Dog.prototype.bark = function() { console.log("wouf wouf"); };

var Doberman = function() {};
extendClass(Doberman, Dog); // using extendClass to makes Doberman inherits from Dog.
new Doberman().bark();  // works as previously : prints "wouf wouf"

Et voila. On est maintenant capable de faire étendre ‘simplement’ un constructeur à un autre constructeur via une méthode réutilisable.

En pratique on peut encore aller plus loin et définir une « Classe » de base qui permettra une déclaration plus facile de nos futur classes hérités :

// definiting the base constructor for all classes, which will execute the final class prototype's initialize method if exists
var Class = function() {
    this.initialize && this.initialize.apply(this, arguments);
};
Class.extend = function(childPrototype) { // defining a static method 'extend'
    var parent = this;
    var child = function() { // the child constructor is a call to its parent's
        return parent.apply(this, arguments);
    };
    child.extend = parent.extend; // adding the extend method to the child class
    var Surrogate = function() {}; // surrogate "trick" as seen previously
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate;
    for(var key in childPrototype){
        child.prototype[key] = childPrototype[key];
    }
    return child; // returning the child class
};

Ce qui nous permet désormais une déclaration de nos « classes » beaucoup plus lisible :

var Dog = Class.extend({
    initialize : function() { // initialize is called by constructor at instanciation.
  this.numberOfLegs = 4;
 },
    bark : function() { console.log("wouf wouf"); }
});

var Doberman = Dog.extend({
   growl : function() { console.log("aouwww"); }
});

var rufus = new Doberman();
// now play with it !

Et voila. Cette dernière approche est beaucoup plus satisfaisante : on peut définir simplement et de manière lisible nos classes javascript.

En pratique, c’est ce qui est utilisé dans presque toute les librairies actuelles : le système d’extension de vue/model de backbone par exemple, utilise simplement une version un peu plus avancé de notre méthode extend. Dans Angular, le système de cascading dans les scope fonctionne aussi comme cela : chaque scope fils a comme prototype le scope parent ! Et avant eux, mootools et prototype (la librairie) proposaient déjà une approche OO avec des méthodes similaires.

C’est également sur ce concept que se basent les transcompiler javascript tels que Coffeescript ou TypeScript pour leur approche objet.

Pour ceux qui veulent aller plus loin, il existe, depuis ECMAScript 5 (sur les navigateurs modernes donc) une alternative viable pour gérer la chaîne de prototype (et pas seulement) : Object.create (et au passage, un autre pointeur dailyjs).

Alors, au final, le prototypage, plutôt simple non ?

Publié par

Publié par Pierre Gayvallet

Passé du coté front-office de la force il y a bien longtemps, Pierre est un passionné de développement Web et d'UX, suivant avec grand intérêt tout ce qui touche à l'écosystème javascript. C'est également un craftsman Backbone aguerri.

Commentaire

47 réponses pour " Javascript, retour aux bases : constructeur, prototype et héritage "

  1. Publié par , Il y a 7 ans

    Dans ton dernier exemple c’est normal que rufus.growl() fasse :
    TypeError: Object [object Object] has no method ‘growl’

  2. Publié par , Il y a 7 ans

    Bonjour,

    Un petit oubli. Il faut effectivement étendre le prototype fils dans la méthode extend :

    for(var key in childPrototype){
    child.prototype[key] = childPrototype[key];
    }

    C’est corrigé, merci.

  3. Publié par , Il y a 7 ans

    Bonjour

    Bonne idée et bon article.

    Sur le Class.extends je ne vois pas l’intérêt du this.constructor = child pour la fonction Surrogate ? Ca marche aussi avec une fonction vide.
    Par ailleurs si dans ton exemple Doberman a aussi un initialize, on ne va appeler que celui-la et pas celui de Dog.
    Mais c’est peut-être pour le prochain article ?

  4. Publié par , Il y a 7 ans

    Bonjour Philippe,

    C’est une bonne remarque, le « this.constructor = […] » ne sert effectivement à rien dans l’exemple proposé. C’est un oubli lors de la simplification de la méthode extend dont je me sert réellement en projets (en pratique cela permet d’utiliser le constructeur parent si l’on choisi de ne pas en définir un pour la classe fille et donc d’avoir un fallback plus « gracieux » que la méthode « initialize » optionnelle utilisée en exemple).

    Je rectifie donc, merci.

    Pour l’initialize qui ne cascade pas, je l’ai rangé dans la problématique des appels ‘super’ qui sera le sujet non pas du suivant, mais du 3eme article.

  5. Publié par , Il y a 7 ans

    Super article qui permet de consolider les bases quand on a appris Javascript « sur le tas ».
    Merci !

  6. Publié par , Il y a 7 ans

    Quand on passe en mode impression (feuille de style d’impression), on perd les couleurs, dommage pour la coloration syntaxique du js…

  7. Publié par , Il y a 7 ans

    Bonjour,

    Excellent article qui m’a permis de vraiment bien comprendre le prototype dans ses notions avancées.

    Par contre petite question, je ne comprends trop l’exemple avec l’objet Class à la fin, déjà que signifie « this.initialize && this.initialize.apply(this, arguments); »?
    Car nous ne sommes pas dans une condition, cela veut dire qu’on applique les fonction, initialize et initialize.apply?

    Sinon d’provient sort initialize, il n’a été défini nul part au debut du code?

    Merci d’avance

  8. Publié par , Il y a 7 ans

    bonjour Faror,

    En fait, l’opérateur && est un « racourci » de « if » souvent utilisé en javascript : l’opérande de droite n’est évalué que si l’opérande de gauche est évaluée vrai, ce qui permet de faire des tests inlines moins verbeux mais plus courts.

    Concrètement,

    this.initialize && this.initialize.apply(this, arguments);

    peut s’écrire de manière plus traditionnelle comme cela :

    if(this.initialize!=null) {
    this.initialize.apply(this, arguments);
    }

    Quand à l’utilisation, cela permet, si l’on transmet une méthode nommé « initialize » a la liste des propriété d’un prototype, de s’en servir comme « constructeur » pour nos classes : si l’on a défini une méthode « initialize » dans la liste des propriété de notre classe, celle-ci est exécutée à l’instanciation, tout simplement.

  9. Publié par , Il y a 7 ans

    Merci beaucoup pour ta réponse si rapide.
    Donc initialize est executée lorsqu’on appelle Class.extend à la ligne « var Dog = Class.extend({« , comme avant on n’a pas encore eu besoin de la classe donc elle n’existe pas encore c’est bien cela?
    Sinon dans ce cas là, le this de « var parent = this; » représente Dog ou Class?

    Merci et mes excuses pour me précédent message mal rédigé et rempli de fautes.

  10. Publié par , Il y a 7 ans

    non justement, initialize est appelé par l’instance, lors du

    var rufus = new Doberman();

    comme un « constructeur » dans le sens « classique » des autres languages OO ( c’est d’ailleurs le comportement qu’on cherche à apporter )

  11. Publié par , Il y a 7 ans

    Merci, le this de « var parent = this; » représente quel objet stp?

  12. Publié par , Il y a 7 ans

    Faror,

    Dans la méthode extend, le « this » de parent = this represente la classe en train d’etre étendu, donc la classe parent

  13. Publié par , Il y a 7 ans

    Merci!
    D’accord je crois avoir tout compris.
    Juste une dernière chose, en fait extend permet aussi de créer un constructeur (par exemple Dog dans l’exemple)?

    Merci d’avance

  14. Publié par , Il y a 7 ans

    Edit: je vous prie de m’excuser pour le double commentaire mais juste pour préciser que le return de:

    var child = function() { // the child constructor is a call to its parent’s
    return parent.apply(this, arguments);
    };

    ne sert à rien, on peut appliquer directement la « fonction » parent.
    D’ailleurs je suis étonné que ca marche avec le return, ca j’ai l’impression que ca n’a pas de sens.

  15. Publié par , Il y a 7 ans

    Faror,

    On arrive hors scope de cet article, mais un constructeur peut avoir une valeur de retour. Dans le cas ou la valeur de retour est une instance dudit constructeur, c’est ce qui sera utiliser au lieu de l’instance elle même. C’est assez peu utilisé, mais peut par exemple servir à faire des factory.

  16. Publié par , Il y a 7 ans

    Belle idée que cette série. Continuez.
    J’attends la suite avec impatience!

    En venant des langages orientés objets classiques, on a cependant un sentiment de complexité quant à l’usage de l’héritage en JS. Tout ça pour ça, serait-on tenté de dire.

    Néanmoins, l’expansion massive des frameworks JS cache cette complexité et dans ce contexte, les explications sur le prototype et l’héritage sont salutaires (on sait sur quoi l’on travaille mais on a n’a pas à mettre les mains dans le cambouis).

  17. Publié par , Il y a 7 ans

    Bonjour,

    bravo pour cet article instructif. J’ai cependant une question. Comment fait-on pour étendre la classe fille en lui donnant des attributs supplémentaires?
    ex:

    var Dog = Class.extend({
    initialize : function() {
    this.numberOfLegs = 4;
    },
    bark : function() { console.log(« wouf wouf »);}
    });

    var DogAllemand = Dog.extend({
    initialize : function() {
    this.size= « big »;
    this.dontlike = « cats »;
    },
    bark : function () { console.log(« wif wif »);}, // nouvelle méthode bark qui sera appelée.
    growl : function() { console.log(« aouwww »);}
    });

    D’avance, merci de votre réponse.

  18. Publié par , Il y a 7 ans

    Bonjour,
    excellent article, pour le débutant en JS que je suis et venant du OO (c++..), donc exactement dans ma problématique du moment.
    Mais j’ai la même question que Johan Chouquet, à savoir: « comment avoir une fonction initialize() dans la classe fille, et donc que les appels a initialize() cascadent..? »
    Vous parlez d’ailleurs d’un article à venir sur ce sujet?
    Merci

  19. Publié par , Il y a 7 ans

    Dans héritage premiere approche il faut ecrire :
    Doberman.prototype.growl

    sinon on obtient :
    Exception: rufus.growl is not a functio

  20. Publié par , Il y a 7 ans

    Bonjour,

    Je suis perplexe quand à ton héritage dans la partie « Attention cependant à ne surtout pas faire cela ». As-tu testé ce que tu affirmes? car dans mon cas, le prototype n’est pas partagé mais peut être utilisé malgré tout de façon bien distinct!

  21. Publié par , Il y a 7 ans

    J’apporte une petite info en plus par rapport à mon commentaire ci-dessus. Ce que je veux dire c’est quand utilisant cette méthode « à ne pas reproduire », on va pouvoir ajouter/modifier le prototype de Dog, mais pas celui de Doberman.

  22. Publié par , Il y a 7 ans

    Merci pour cet article, j’ai eu un peu mal au crane sur la fin, une petite relecture demain ou après demain va s’imposer pour bien assimiler tout cela.

  23. Publié par , Il y a 7 ans

    l’affectation d’une instance à un prototype change la propriété _proto_ du prototype

  24. Publié par , Il y a 7 ans

    Salut Pierre,

    même constat que toto pour la partie « héritage, première approche ».
    L’ajout de la méthode ‘growl’ à Doberman ne retourne pas d’erreur, mais son utilisation lève une exception. La méthode ne semble pas être ajoutée.
    Tu aurais une explication ?

    Merci pour cet excellent billet que je découvre avec plaisir.

  25. Publié par , Il y a 7 ans

    Un grand merci pour cet article super clair et instructif !!!

  26. Publié par , Il y a 6 ans

    Un article vraiment intéressant, aux antipodes des tutoriels bidons que l’on peut trouver d’habitude et qui ne sont que des redites de documentations existantes (Ctrl+c, Ctrl+v) sans le moindre effort pédagogique. J’en veux des comme ça! Encore….encore…

  27. Publié par , Il y a 6 ans

    Bonjour,
    Excellent article !
    Inspiré, pour étendre mes « classes », mais sans utiliser la syntaxe JSON, je propose :

    function Extend(parent,init){
    var child=function(){
    parent.apply(this,arguments);
    init();
    };
    var Surrogate=function(){};
    Surrogate.prototype=parent.prototype;
    child.prototype=new Surrogate();
    return child;
    };

    Qui s’utilise comme suit :

    var Child=Extend(Parent,function(){
    this.childVar=’Bonjour !’;
    });

    Des objections ?

  28. Publié par , Il y a 6 ans

    Ca fait réellement plaisir de trouver des articles de qualité !

    Merci

  29. Publié par , Il y a 6 ans

    Bonjour,

    Pour ceux qui ont remarqué l’erreur sur l’exemple de growl, il faut écrire :
    Doberman.prototype.growl = function() { …
    et non :
    Doberman.growl = function() { …

    Puisque justement, on ajoute une fonction au prototype de Doberman, comme on a ajouté la fonction bark() au prototype de Dog juste au dessus.

  30. Publié par , Il y a 6 ans

    Bonjour,

    Super article ! merci beaucoup.

    Mais j’ai une petite question cependant …
    Je ne vois pas pourquoi en effet on prototype à partir du prototype … quand il suffisait de prototyper à partir de l’objet …

    var Animal = function() {
    this.cri = ‘the cri qui tue’;
    this.getCri = function(){
    console.log(this.cri);
    };
    };
    var Mamifere = function() {
    this.hurler = function() {
    console.log(‘a sound’);
    };
    };
    Mamifere.prototype = new Animal();
    var Cow = function (){
    this.parler = function() {
    console.log(‘meuh’);
    };
    };
    Cow.prototype = new Mamifere();

    var sisi = new Cow();

    ça marche bien et c’est beaucoup plus simple !

    voici ce que ça donne dans la console par exemple :

    on rentre sisi et on obtient :
    Cow {parler: function, hurler: function, cri: « the cri qui tue », getCri: function, pattes: 4}
    parler: function () {
    __proto__: Mamifere
    hurler: function () {
    __proto__: Animal
    cri: « the cri qui tue »
    getCri: function (){
    __proto__: Object
    constructor: function () {
    pattes: 4
    __proto__: Object

    Quels sont les désavantages de cette façon de faire ?
    Merci d’avance de la réponse !
    Bien cordialement,
    Brice

  31. Publié par , Il y a 6 ans

    Alors moi je suis passé sur TypeScript, ben voilà, ça facilite un peu la vie :D

  32. Publié par , Il y a 6 ans

    Bonjour,

    Merci pour l’article, mais il m embrouille autant qu il me fait avancer…. la faute a ce surrogate, dont je ne vois pas du tout l utilité dans l ‘exemple et qui semble etre justifié par cette phrase « un prototype ne peut lui même avoir de prototype ». Si j ai bien compris :

    Un prototype et une fonction qu on va utiliser comme modele, comme patron pour créer des objets (a l instar d une classe en java).

    Il est possible de chaîner ces modeles en logeant dans la variable __proto__ la reference d’un autre patron comme dans « Doberman.prototype = Dog.prototype » ce qui permettra aux objets crée a partir du constructeur de doberman d acceder aux propriete / methode déclaré au niveau de Dog. Mais dans ce cas de figure un prototype à un prototype ? le prototype doberman a le prototype dog, ou est ce que je m embrouille les idées ?

    De plus l exemple avec surrogate fonctionne parfaitement bien si on le retire.

    J ai fais des recherches sur surrogate, apriori c est une astuve utilisé dans backbone pour remplacer le ‘super’ de java qui permet de chainer le constructeur du parents. Arretez moi si je me trompe mais ne melangez vous pas 2 problematiques (faire de l heritage / activer le constructeur du parent) dans l ‘exemple, en me mentionnant que l aspect héritage et non le rempalcement de super ?

    Merci pour votre reponse car la, j ai encore plus d interrogation qu hier ;)

  33. Publié par , Il y a 6 ans

    Bonjour,

    Merci pour ce superbe article, une seule question cependant :
    var Surrogate = function() {
    this.constructor = Doberman;
    };

    À quoi sert this.constructor = Doberman; si je le commente ça marche toujours ?

  34. Publié par , Il y a 5 ans

    Bonjour,

    Super explications, je reposte juste la question posée par Brice en Avril 2014 qui n’a tjs pas eu de réponse et me semble effectivement très interessante, je souhaiterais également avoir réponse aux interrogations de brice ! :

    « Bonjour,

    Super article ! merci beaucoup.

    Mais j’ai une petite question cependant …
    Je ne vois pas pourquoi en effet on prototype à partir du prototype … quand il suffisait de prototyper à partir de l’objet …

    var Animal = function() {
    this.cri = ‘the cri qui tue’;
    this.getCri = function(){
    console.log(this.cri);
    };
    };
    var Mamifere = function() {
    this.hurler = function() {
    console.log(‘a sound’);
    };
    };
    Mamifere.prototype = new Animal();
    var Cow = function (){
    this.parler = function() {
    console.log(‘meuh’);
    };
    };
    Cow.prototype = new Mamifere();

    var sisi = new Cow();

    ça marche bien et c’est beaucoup plus simple !

    voici ce que ça donne dans la console par exemple :

    on rentre sisi et on obtient :
    Cow {parler: function, hurler: function, cri: « the cri qui tue », getCri: function, pattes: 4}
    parler: function () {
    __proto__: Mamifere
    hurler: function () {
    __proto__: Animal
    cri: « the cri qui tue »
    getCri: function (){
    __proto__: Object
    constructor: function () {
    pattes: 4
    __proto__: Object

    Quels sont les désavantages de cette façon de faire ?
    Merci d’avance de la réponse !
    Bien cordialement,
    Brice »

  35. Publié par , Il y a 5 ans

    Excellent article, merci beaucoup!
    Let’s Javascript!

  36. Publié par , Il y a 5 ans

    Article très bien fait. Bravo!

    Au passage dans le bout de code de votre article j’ai un souci:

    var Dog = function() {}; // defining Dog constructor and prototype
    Dog.prototype.bark = function() { alert(« wouf wouf »); };
    var Doberman = function() {}; // defining Doberman constructor
    for(key in Dog.prototype) { // copying all Dog prototype property into the Doberman property
    Doberman.prototype[key] = Dog.prototype[key];
    }
    Doberman.growl = function() { alert(« aouww »); }; // then adding some Doberman specific functions
    var rufus = new Doberman();
    rufus.bark(); // using the Dog bark method, prints « wouf wouf »;
    rufus.growl(); // using the Doberman growl method, prints « aouww »;

    Il ne fonctionne que si je remplace
    Doberman.growl = function() { alert(« aouww »); };
    par
    Doberman.prototype.growl = function() { alert(« aouww »); };

    Des idées?

    Merci en tout cas.

    P.S.:
    une petite faute aperçue -> (même ce n’est pas exactement ca, mais nous allons le voir juste en dessous)
    ->
    (même ->siç<-a, mais nous allons le voir juste en dessous)

  37. Publié par , Il y a 5 ans

    var Class = function() {
    this.initialize && this.initialize.apply(this, arguments);
    };
    Je ne comprends pas l’utilité de cette ligne de code : l’utilisation de l’opérateur  » &&  » induit que cette expression renvoie un booléen. Pourquoi ?
    Aussi, dans cette exemple il n’y a pas trace de la définition de « initialize » et « apply ».

    Aussi, juste après, vous écrivez
    [Ce qui nous permet désormais une déclaration de nos « classes » beaucoup plus lisible :]

    Et vous utilisez la notation avec  » :  » pour définir des attributs ou méthodes. je n’ai pas suivi la logique du passage de l’un à l’autre…
    D’avance merci de vos explications
    David

  38. Publié par , Il y a 5 ans

    Quand nous disons que, souvent, les développeurs JS ne connaissent pas JS, on comprend mieux pourquoi. Je trouve personnellement tout cela inutilement complexe. Vraiment. Sans troll aucun. Comment ne pas s’y perdre dans ses conditions.

  39. Publié par , Il y a 5 ans

    Bonjour, encore merci pour cet article.

    Serait-il possible d’avoir une réponse au commentaire de « brice
    AVR 23RD, 2014 / 9:40 » et à celui de « Jeremy
    NOV 22ND, 2014 / 14:48 »

    Bien cordialement

  40. Publié par , Il y a 4 ans

    c’est un bon article , c’est super qu’il existe !

    Mais le premier exemple d’héritage est faux :

    Doberman.growl = function() { console.log(« aouww »); }; // ON NE PEUT PAS AJOUTER UNE FONCTION AINSI
    var rufus = new Doberman();
    rufus.bark(); // using the Dog bark method, prints « wouf wouf »;
    rufus.growl(); // ERREUR : TypeError: rufus.growl is not a function

  41. Publié par , Il y a 4 ans

    Salut Pierre et merci pour cet article très instructif ! cependant serait il possible d’en ajouter d’autres, dont un en reprenant vraiment les bases du Javascript suivi des bonnes pratiques actuelles ? merci :)

  42. Publié par , Il y a 4 ans

    Article aussi intéressant que précieux, merci beaucoup !

  43. Publié par , Il y a 4 ans

    Merci !
    C’est corrigé.
    Bonne journée

  44. Publié par , Il y a 4 ans

    très bon article , merci bien.

  45. Publié par , Il y a 4 ans

    Trés interessant…
    Il me manque juste la possibilité de surcharger un méthode en redéfinissant la méthode sur l’objet et, dans celui-ci, d’appeller la méthode de l’objet hérité.
    J’essaye de voir comment faire mais je ne trouve pas.
    Est ce que cela est possible… ?
    MErci

  46. Publié par , Il y a 4 ans

    En fait, on peut rajouter une propriété à l’objet de cette manière:
    child.prototype[« parent »] = parent.prototype;
    Du coup, si l’on surcharge une méthode, on peut tout de même appeler la méthode parent via cet objet…

  47. Publié par , Il y a 3 ans

    En fait ça marche au poil,
    mon problème venait d’autre part

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.