Onze idées fausses sur l'héritage en JavaScript

une traduction de l'article « Common Misconceptions About Inheritance in JavaScript » écrit par Eric Elliott

Eric Elliott

Traducteur

Sylvain Pollet-Villard

WAT? [wat] — interjection : le son que fait un programmeur quand quelque chose viole le principe du moindre étonnement en l'étonnant avec un comportement contre-intuitif.

> .1 + .2

0.30000000000000004

> WAT? OMG! STFU! STUPIDE JAVASCRIPT!!!

…

Par ailleurs, WAT? est également le son que je fais quand je discute avec beaucoup de développeurs JavaScript chevronnés qui ont négligé l'apprentissage des mécanismes de base de l'héritage par prototype : l'une des plus importantes innovations dans l'histoire de l'informatique et l'un des Deux Piliers du JavaScript.

D'après moi, ce serait comme un photographe professionnel qui aurait encore à apprendre le triangle d'exposition — la formule basique pour contrôler le style visuel d'une photographie. Simplement :

Si vous ne comprenez pas les prototypes, vous ne comprenez pas JavaScript.
Commentez

- Titre : Onze idées fausses sur l'héritage en JavaScript

- Auteur : Eric Elliott

- Traducteur : Sylvain Pollet-Villard

- Parution : 23 mai 2015

- Licence : Copyright ® 2015 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'héritage par classe et l'héritage par prototype sont équivalents, c'est juste une préférence de style

- II. Les classes sont la bonne manière de créer des objets en JavaScript

- III. On a besoin d'une fonction constructeur pour définir le comportement de l'instanciation et initialiser l'objet

- IV. On a besoin des fonctions constructeur pour privatiser les variables

- V. « new » signifie que le code utilise de l'héritage par classe

- V-A. « instanceof » est un mensonge

- V-B. « new » est bizarre

- VI. Il y a une grosse différence de performances entre l'héritage par classe et l'héritage par prototype

- VII. Il y a une grosse différence de consommation mémoire entre l'héritage par classe et par prototype

- VIII. Les API natives utilisent les constructeurs, donc ceux-ci sont plus couramment utilisés que les fonctions usines

- IX. L'héritage par classe est plus naturel en JavaScript que l'héritage par prototype

- IX-A. L'histoire en bref des bibliothèques populaires en JavaScript

- X. Le choix entre l'héritage par classe et par prototype dépend du cas d'utilisation

- XI. ES6 a le mot-clé « class » et nous devrions tous l'utiliser

- XII. Remerciements

I - L'héritage par classe et l'héritage par prototype sont équivalents, c'est juste une préférence de style

Non.

L'héritage par classe et l'héritage par prototype sont fondamentalement et sémantiquement distincts.

Il y a des caractéristiques bien définies entre l'héritage par classe et l'héritage par prototype. Afin que la moindre phrase de cet article présente un sens, vous devez garder en tête ces points.

Avec l'héritage par classe, les instances héritent d'un schéma (la classe) et créent une relation entre classe et sous-classe. En d'autres termes, vous ne pouvez pas utiliser la classe de la même façon que vous utilisez l'instance. Vous ne pouvez pas appeler les méthodes de l'instance sur la définition de la classe elle-même. Vous devez d'abord créer une instance puis appeler les méthodes sur cette instance.

Avec l'héritage par prototype, les instances héritent d'autres instances. Utiliser des prototypes délégués (prendre un objet comme exemple pour être le prototype d'une instance) suit littéralement le principe des objets liés à d'autres objets (Objects Linking to Other Objects, ou OLOO, comme l'appelle Kyle Simpson). Utiliser de l'héritage par concaténation revient à copier les propriétés du prototype vers la nouvelle instance.

C'est très important que vous compreniez ces différences. L'héritage par classe, en vertu de ces mécanismes, crée une hiérarchie de classes en plus de créer la sous-classe.De ces hiérarchies résulte un code ankylosé, difficile à modifier et fragile (facile à casser à cause des effets de bord lorsque vous modifiez les classes de base).

L'héritage par prototype ne crée pas nécessairement ce genre de hiérarchies. Je vous recommande de garder les chaînes de prototypes les moins longues possible. Il est facile de mettre à plat de nombreux prototypes ensemble pour former un seul prototype délégué.

En bref :

- une classe est un modèle, un schéma ;

- un prototype est une instance, un objet.

II - Les classes sont la bonne manière de créer des objets en JavaScript

Non.

Il y a plusieurs bonnes façons de créer des objets en JavaScript. La première et la plus commune est l'écriture littérale. Ça ressemble à ça (en ECMAScript 6, alias ES6) :

// ES6 / ES2015
let mouse = {
furColor: 'brown',
legs: 4,
tail: 'long, skinny',
describe () {
return `A mouse with ${this.furColor} fur,
${this.legs} legs, and a ${this.tail} tail.`;
}
};

Bien sûr, l'écriture littérale existe depuis bien plus longtemps que ES6, mais il manquait la syntaxe raccourcie pour décrire des méthodes, et vous deviez utiliser 'var' au lieu de 'let'. Oh, et la notation de template dans la chaîne de caractères de la fonction describe n'existe pas non plus en ECMAScript 5 (ES5).

Vous pouvez attacher des prototypes délégués avec Object.create() (une fonctionnalité ES5) :

let animal = {
animalType: 'animal',

describe () {
return `An ${this.animalType}, with ${this.furColor} fur,
${this.legs} legs, and a ${this.tail} tail.`;
}
};

let mouse = Object.assign(Object.create(animal), {
animalType: 'mouse',
furColor: 'brown',
legs: 4,
tail: 'long, skinny'
});

Décomposons un peu ce code. animal est un prototype délégué. mouse est une instance. Quand vous essayez d'accéder à une propriété de mouse qui est inexistante, le moteur JavaScript va chercher la propriété sur animal (le délégué).

Object.assign() est une fonctionnalité ES6 sous la tutelle de Rick Waldron qui était auparavant implémentée dans une petite douzaine de bibliothèques. Vous devez la connaître comme $.extend() de jQuery ou _.extend() de Underscore. Lodash en a une version appelée assign(). Vous lui passez un objet de destination, et autant d'objets sources que vous voulez, séparés par des virgules. Cela copiera toutes les propriétés propres et énumérables en les assignant depuis les objets sources jusqu'à l'objet de destination. Si plusieurs propriétés sont en conflit, la version du dernier objet aura le dessus.

Object.create() est une fonctionnalité ES5 sous la tutelle de Douglas Crockford qui vous permet d'attacher des prototypes délégués sans devoir utiliser des constructeurs ou le mot-clé new.

Je vais passer outre l'exemple de la fonction constructeur parce que je ne peux pas le recommander. Je l'ai souvent vu utiliser abusivement, et causer beaucoup de dégâts. Il est à noter que beaucoup de gens intelligents sont en désaccord avec moi. Les gens intelligents feront ce qu'ils veulent.

Les gens sages écouteront le conseil de Douglas Crockford :

« Si une fonctionnalité est parfois dangereuse et qu'il y a une meilleure option, alors utilisez toujours la meilleure option. »

III - On a besoin d'une fonction constructeur pour définir le comportement de l'instanciation et initialiser l'objet

Non.

N'importe quelle fonction peut créer et retourner des objets. Quand il ne s'agit pas d'une fonction constructeur, on l'appelle une fonction usine (factory function).

La Meilleure Option

let animal = {
animalType: 'animal',

describe () {
return `An ${this.animalType} with ${this.furColor} fur,
${this.legs} legs, and a ${this.tail} tail.`;
}
};

let mouseFactory = function mouseFactory () {
return Object.assign(Object.create(animal), {
animalType: 'mouse',
furColor: 'brown',
legs: 4,
tail: 'long, skinny'
});
};

let mickey = mouseFactory();

Je n'appelle généralement pas mes fonctions d'usine “factory” —  c'est juste pour illustrer. Normalement, je l'aurais simplement appelée mouse().

IV - On a besoin des fonctions constructeur pour privatiser les variables

Non.

En JavaScript, chaque fois que vous exportez une fonction, cette fonction a accès aux variables au-delà de son corps de définition. Quand vous les utilisez, le moteur JS crée une fermeture (closure). Les fermetures sont un schéma courant en JavaScript, et elles sont couramment utilisées pour la privatisation de données.

Les fermetures ne sont pas réservées aux fonctions constructeur. N'importe quelle fonction peut créer une fermeture pour gérer des données privées :

let animal = {
animalType: 'animal',

describe () {
return `An ${this.animalType} with ${this.furColor} fur,
${this.legs} legs, and a ${this.tail} tail.`;
}
};

let mouseFactory = function mouseFactory () {
let secret = 'secret agent';

return Object.assign(Object.create(animal), {
animalType: 'mouse',
furColor: 'brown',
legs: 4,
tail: 'long, skinny',
profession () {
return secret;
}
});
};

let james = mouseFactory();

V - « new » signifie que le code utilise de l'héritage par classe

Non.

Le mot-clé new est utilisé pour invoquer une fonction constructeur. Ce qu'il fait concrètement est :

- créer une nouvelle instance ;

- attacher le mot-clé this dans la fonction constructeur à la nouvelle instance ;

- assigner par référence le prototype délégué du nouvel objet à l'objet référencé par la propriété prototype de la fonction constructeur ;

- nommer le type de l'objet comme le constructeur, ce que vous remarquerez le plus souvent dans la console de débogage. Vous verrez [Object Foo], par exemple, au lieu de [Object object] ;

- permettre à instanceof de vérifier ou non si la référence du prototype de l'objet est la même que celle pointant vers la propriété .prototype de la fonction constructeur.

V-A - « instanceof » est un mensonge

Faisons une pause ici un moment pour reconsidérer la valeur du mot-clé instanceof. Vous pourriez changer d'avis quant à son utilité.

Important : instanceof ne vérifie pas le type de la façon à laquelle vous pourriez vous attendre, comme pour les autres langages à typage fort. Au lieu de ça, il vérifie l'identité du prototype, et on peut facilement duper l'opérateur. Cela peut ne pas fonctionner entre plusieurs contextes d'exécution par exemple (ce qui est généralement une source de bogues, de frustrations et de limitations inutiles). Un exemple dans la nature, sur bacon.js.

On peut aussi facilement le manipuler en faux positifs, et (plus souvent) en faux négatifs à partir d'autres sources. Puisqu'il s'agit d'un test d'identité sur la propriété .prototype de l'objet ciblé, cela peut amener de drôles de choses :

> function foo() {}
> var bar = { a: 'a'};
> foo.prototype = bar; // Object {a: 'a'}
> baz = Object.create(bar); // Object {a: 'a'}
> baz instanceof foo // true. oops.

Le dernier résultat est en accord complet avec la spécification JavaScript. Rien n'est cassé  — c'est juste que instanceof ne peut pas garantir la fiabilité du type. On peut le tromper facilement avec à la fois des faux positifs et faux négatifs.

Au-delà de ça, essayer de forcer votre code JS à se comporter comme un code fortement typé peut empêcher vos fonctions d'être déclinées en des versions plus génériques, qui sont bien plus utiles et réutilisables.

instanceof limite la réutilisabilité de votre code, et introduit potentiellement des bogues dans les programmes qui utilisent votre code.

« instanceof » est un mensonge. Optez pour le duck typing.

V-B - « new » est bizarre

WAT? new fait aussi de drôles de choses pour retourner des valeurs. Si vous essayez de retourner une primitive, cela ne fonctionnera pas. Si vous retournez un objet arbitrairement, cela fonctionnera, mais this sera jeté aux oubliettes, en perdant toute référence à lui (y compris avec .call() et .apply()), et cassant le lien avec la propriété .prototype du constructeur.

VI - Il y a une grosse différence de performances entre l'héritage par classe et l'héritage par prototype

Non.

Vous avez peut-être entendu parler des classes cachées, et pensez que les constructeurs battent à plate couture en performances les objets instanciés avec Object.create(). Ces différences de performances sont largement surestimées.

Une petite fraction du temps d'exécution de votre application est passée à exécuter le JavaScript, et une fraction minuscule de ce temps est passée à accéder aux propriétés des objets. En fait, même les ordinateurs les plus lents produits aujourd'hui peuvent accéder à des millions de propriétés par seconde.

Ce n'est pas le goulot d'étranglement de votre application. Faites-vous une faveur et mesurez la performance de votre application pour identifier les réels goulots d'étranglement. Je suis sûr qu'il y a un million de choses que vous devriez revoir avant de penser à ce genre de micro-optimisations.

Pas convaincu ? Pour qu'une micro-optimisation ait un impact appréciable sur votre application, vous devez avoir à répéter l'opération des centaines de milliers de fois, et les seules différences auxquelles vous devriez vous soucier sont encore un ordre de magnitude plus loin.

Règle d'or : analysez votre application et éliminez autant que possible les chargements, requêtes réseau, lectures/écritures de fichiers et goulots d'étranglement dans les fonctions de rendu, autant que vous pouvez en trouver. Ensuite et seulement ensuite, vous pouvez commencer à penser aux micro-optimisations.

Pouvez-vous dire la différence entre 0,0000000001 seconde et 0,000000001 seconde ? Moi non plus, mais je peux dire pour sûr la différence entre charger dix petites icônes et charger une police de caractères iconiques.

Si vous avez analysé votre application et avez constaté que la création d'objets était un goulot d'étranglement pour vous, alors la méthode la plus performante n'est pas d'utiliser new et la programmation objet par classe. La méthode la plus rapide est d'utiliser l'écriture littérale. Vous pouvez l'utiliser à l'intérieur d'une boucle et ajouter les objets à une collection pour éviter qu'ils soient jetés par le collecteur de déchets (garbage collector). Si cela vaut la peine d'abandonner la programmation objet orientée prototype pour des questions de performances, alors cela vaut la peine d'abandonner toute chaîne d'héritage pour ne plus utiliser que des objets déclarés littéralement.

Mais Google a dit que les classes étaient rapides…

WAT? Google conçoit un moteur JavaScript. Vous concevez une application. Évidemment vous vous souciez de choses bien différentes. Laissez Google gérer les micro-optimisations. Souciez-vous des réels goulots d'étranglement de votre application. Je vous le promets, vous aurez un bien meilleur retour sur investissement en vous concentrant sur autre chose.

VII - Il y a une grosse différence de consommation mémoire entre l'héritage par classe et par prototype

Non.

Les deux peuvent utiliser des prototypes délégués pour partager des méthodes entre de nombreuses instances d'objets. Les deux peuvent exploiter ou éviter les fermetures pour encapsuler un tas de variables d'état.

En fait, si vous commencez avec des fonctions usines, c'est plus facile de basculer ensuite sur des collections d'objets afin que vous puissiez gérer la mémoire plus attentivement et éviter d'être périodiquement bloqué par le passage du garbage collector. Pour en savoir plus sur la raison pour laquelle faire la même chose avec les constructeurs est plus délicat, lisez la note  WAT? sur l'opérateur new.

En d'autres mots, si vous voulez plus de flexibilité pour la gestion de la mémoire, optez pour des fonctions usines au lieu de constructeurs et d'héritage par classe.

« … si vous voulez plus de flexibilité pour la gestion de la mémoire, optez pour des fonctions usines… »

VIII - Les API natives utilisent les constructeurs, donc ceux-ci sont plus couramment utilisés que les fonctions usines

Non.

Les fonctions usines sont extrêmement communes en JavaScript. Par exemple, la bibliothèque JavaScript la plus populaire de tous les temps, jQuery expose une fonction usine à ses utilisateurs. John Resig a écrit à propos de ce choix d'utiliser une fonction usine et l'extension de prototypes plutôt qu'une classe. Basiquement, cela se réduit au fait qu'il ne voulait pas que les appelants aient à taper new à chaque fois qu'ils voulaient sélectionner un élément. À quoi cela aurait-il ressemblé ?

/**
classy jQuery - une réalité alternative où jQuery craint et n'a jamais décollé
OU
Pourquoi personne n'aurait aimé jQuery s'il avait exporté une classe plutôt qu'une fonction usine.
**/

/* Cela a juste l'air débile. Est-ce que l'on crée un nouvel élément avec l'id foo ?
Non. On sélectionne un élément existant du DOM, et on le place dans une instance d'objet jQuery, */
var $foo = new $('#foo');

// De plus, cela nous oblige à taper plus pour un gain NUL.
var $bar = new $('.bar');
var $baz = new $('.baz');

// Et ça c'est… ben, je sais pas quoi.
var $bif = new $('.foo').on('click', function () {
var $this = new $(this);
$this.html('clicked!');
});

Qui d'autre expose des fonctions usines ?

- React : React.createClass() est une usine.

- Angular utilise des classes et des usines, mais les enveloppe toutes avec une usine dans son conteneur d'injection de dépendances. Tous les services Angular sont du sucre syntaxique utilisant la fonction usine .provider(). Il y a même un service .factory(), et le service .service() enveloppe les constructeurs normaux et expose… vous l'avez deviné : une usine pour les utilisateurs de l'injection de dépendances.

- Ember :Ember.Application.create() est une usine produisant une application. Plutôt que de créer des constructeurs à appeler avec new, la méthode .extend()vient étendre l'application.

- Node : les services du cœur de Node comme http.createServer() et net.createServer() sont des fonctions usines.

- Express est une fonction usine pour créer une application express.

Comme vous le voyez, presque toutes les bibliothèques et tous les frameworks populaires en JavaScript utilisent largement les fonctions usines. Le seul schéma d'instanciation d'objets plus commun que les fonctions usines est l'écriture littérale.

Les API natives de JavaScript ont commencé en utilisant les constructeurs parce que l'on a demandé à Brendan Eich de le faire davantage ressembler à Java (voir l'histoire de JavaScript). JavaScript a continué à utiliser les constructeurs par cohérence avec lui-même. Cela serait délicat de tout changer pour des fonctions usines et de déprécier les constructeurs aujourd'hui.

Cela ne veut pas dire que vos API doivent être mal fichues.

IX - L'héritage par classe est plus naturel en JavaScript que l'héritage par prototype

Non.

Chaque fois que j'entends cette idée fausse, je suis tenté de répondre “do u even JavaScript?” et de m'en aller… mais je résiste à l'envie et remets plutôt les pendules à l'heure.

Ne vous sentez pas mal si c'est aussi votre question. Ce n'est pas de votre faute. Les ressources pour apprendre le JavaScript sont nulles !

La réponse à cette question est un grand, gigantissime

Non… (mais)

Les prototypes sont le paradigme idiomatique de l'héritage en JavaScript, et les classes sont des espèces envahissantes, des mauvaises herbes.

IX-A - L'histoire en bref des bibliothèques populaires en JavaScript

À l'origine, tout le monde écrivait ses propres bibliothèques, et le partage public était minoritaire. Puis Prototype est arrivé. (Le nom est un bon indice ici.) Prototype faisait son tour de magie en étendant les prototypes délégués inclus dans les navigateurs en utilisant de l'héritage par concaténation.

Plus tard, nous avons tous réalisé que modifier les prototypes natifs était un antimodèle et que les alternatives natives en conflit avec les bibliothèques cassaient l'Internet. Mais c'est une autre histoire.

Le suivant sur la montagne russe de la popularité des biblis JS a été jQuery. L'appel au succès de jQuery a été les plugins jQuery. Ils fonctionnaient en étendant le prototype délégué de jQuery en utilisant de l'héritage par concaténation.

Est-ce que vous commencez à voir un schéma ici ?

jQuery reste la bibliothèque JavaScript la plus populaire jamais conçue. Avec une LARGE marge. LARGE.

C'est là où les choses sont devenues confuses et que l'extension de classes a commencé à se faufiler dans le langage… John Resig (auteur de jQuery) a écrit un billet Héritage simple par classes en JavaScript, et les gens ont effectivement commencé à l'utiliser, malgré que John Resig lui-même ne pensait pas que cela avait sa place dans jQuery (parce que la programmation objet orientée prototype fait le même boulot en mieux).

Des semi-populaires et Java-esques frameworks comme ExtJS sont apparus, inaugurant les prémices de l'usage moyennement ordinaire des classes en JavaScript. C'était en 2007. JavaScript avait alors 12 ans avant qu'une quelque peu populaire bibliothèque commence à exposer aux utilisateurs de l'héritage par classes.

Trois ans plus tard, Backbone a explosé et avait une méthode .extend() qui imitait l'héritage par classe, en incluant toutes les vilaines fonctionnalités comme les hiérarchies fragiles d'objets. Voilà où l'enfer se déchaîna.

Une application d'environ 100 000 lignes de code a été réalisée avec Backbone. Quelques mois après le début du projet, j'ai débogué une hiérarchie à six niveaux en essayant de trouver un bug. Je suis passé sur chaque ligne de code du constructeur en remontant peu à peu la chaîne d'héritage. J'ai finalement trouvé et corrigé le bug sur la classe de plus haut niveau. Ensuite, j'ai eu à corriger un tas de classes enfants parce qu'elles dépendaient du comportement bogué de la classe de base. Des heures de frustration qui auraient dû être une correction de cinq minutes.

Ce n'est pas JavaScript. Je me suis soudainement retrouvé à vivre de nouveau dans l'enfer Java. Cet endroit de solitude, sombre et effrayant, où chaque mouvement rapide peut faire frémir des hiérarchies entières qui s'effondrent avec fracas, nous laissant agonisant et pris de fortes convulsions.

Voilà de quoi sont faites les refontes complètes d'applications.

Mais, perdu dans la documentation de Backbone, un rayon de soleil d'or :

Un rayon de soleil dans la tanière de la bête…

var object = {};

_.extend(object, Backbone.Events);

object.on("alert", function(msg) {
alert("Triggered " + msg);
});

object.trigger("alert", "an event");

Notre bon vieil ami, l'héritage par concaténation, nous égaye la journée avec un mixin Backbone.Events.

Il s'avère que, si vous regardez n'importe quelle bibliothèque JavaScript non triviale d'assez près, vous y trouverez des exemples de concaténation et de délégation. C'est tellement commun et automatique pour les développeurs JavaScript qu'ils le font sans même y penser comme à de l'héritage, même si cela accomplit le même objectif.

L'héritage en JavaScript est tellement facile qu'il rend confus les gens qui s'attendent à ce que cela demande des efforts. Pour le rendre plus difficile, nous avons ajouté « class ».

Et comment avons-nous ajouté class ? Nous l'avons conçu par-dessus l'héritage par prototype en utilisant des prototypes délégués et de la concaténation d'objets bien entendu !

C'est comme rouler avec votre Tesla Model S jusqu'à un vendeur de voitures pour l'échanger contre une Ford Pinto rouillée de 1983.

X - Le choix entre l'héritage par classe et par prototype dépend du cas d'utilisation

Non.

La programmation objet orientée prototype est plus simple, plus flexible, et bien moins sujette aux erreurs de conception. J'ai tenu ce discours et encouragé les gens à venir vers moi avec un cas d'usage montrant la supériorité des classes pendant de nombreuses années. Des centaines de milliers de personnes ont entendu cet appel. Le peu de réponses que j'ai reçues dépendaient d'une ou plusieurs idées fausses adressées dans cet article.

J'ai été jadis un fan de l'héritage par classes. J'achetais l'idée complètement. Je construisais des hiérarchies d'objets partout. J'ai conçu des outils de développement d'applications rapides en orienté objet pour aider les architectes logiciels à concevoir des hiérarchies d'objets et des relations qui prenaient du sens. Il fallait un outil visuel pour vraiment pouvoir cartographier toutes les relations d'objets dans les applications pour entreprise ayant cette taxonomie d'héritage par classes.

Peu après ma transition de C++/Java à JavaScript, j'ai arrêté de faire tout ça. Non pas parce que je construisais des applications moins complexes (au contraire), mais parce que JavaScript était tellement plus simple que je n'avais plus besoin de tout cet outillage de conception OO.

Je faisais auparavant du conseil en conception d'applications et recommandait souvent des réécritures radicales. Pourquoi ? Parce que toutes les hiérarchies d'objets deviennent à la longue inadaptées aux nouveaux cas d'utilisation.

Je n'étais pas le seul. En ce temps, les réécritures complètes étaient très courantes pour les nouvelles versions de logiciels. La plupart de ces réécritures étaient nécessaires à cause du verrouillage causé par des hiérarchies de classes fragiles et ankylosées. Des livres entiers ont été écrits sur les erreurs de conception OO et comment les éviter ou les rectifier. Il semblait que chaque développeur avait une copie de “Design Patterns” sur son bureau.

Je vous recommande de suivre le conseil du Gang of Four sur ce point :

« Favoriser la composition d'objets par rapport à l'héritage par classes. »

En Java, c'était plus difficile, car vous aviez toujours à utiliser des classes pour y parvenir.

En JavaScript, nous n'avons pas cette excuse. C'est en fait bien plus simple en JavaScript de simplement créer les objets dont vous avez besoin en assemblant divers prototypes plutôt que de gérer des hiérarchies d'objets.

WAT? Sérieusement. Vous voulez que l'objet jQuery puisse changer un input en megaCalendarWidget ? Vous n'avez pas à extend une class. JavaScript a l'extension dynamique d'objets, et jQuery expose son propre prototype afin que vous puissiez l'étendre — sans un mot-clé extend ! WAT? :

/*
Comment étendre le prototype de jQuery :
Tellement difficile.
Mon cerveau me fait mal.
ouch.
*/

jQuery.fn.megaCalendarWidget = megaCalendarWidget;

// omg je suis si content que ce soit fini

La prochaine fois que vous appellerez la fonction usine jQuery, vous obtiendrez une instance qui peut rendre vos inputs méga bien. (Note du traducteur : c'est ici une plaisanterie de l'auteur, car les widgets datepicker sont connus pour rendre furieux les utilisateurs sur smartphone/tablettes ; utilisez des polyfills sur <input type='date'>.)

De manière similaire, vous pouvez utiliser Object.assign() pour composer un nombre quelconque d'objets ensemble :

import ninja from 'ninja'; // ES6 modules
import mouse from 'mouse';

let ninjamouse = Object.assign({}, mouse, ninja);

Non, vraiment — n'importe quel nombre d'objets :

// Je ne suis pas sûr que Object.assign() soit disponible (ES6)
// donc cette fois j'utilise Lodash. C'est comme Underscore,
// mais en 200 % mieux. Vous pouvez aussi utiliser
// jQuery.extend() ou Underscore's _.extend()
var assign = require('lodash/object/assign');

var skydiving = require('skydiving');
var ninja = require('ninja');
var mouse = require('mouse');
var wingsuit = require('wingsuit');

// La quantité de génialitude qui suit peut être trop importante
// pour les seniors à problèmes cardiaques ou les jeunes enfants.
var skydivingNinjaMouseWithWingsuit = assign({}, // crée un nouvel objet
skydiving, ninja, mouse, wingsuit); // copier toute la coolitude dedans

Cette technique est appelée héritage par concaténation, et les prototypes dont vous héritez sont parfois appelés prototypes exemplaires, ce qui les différencie des prototypes délégués dans le sens où vous les copiez, plutôt que de leur déléguer le travail.

XI - ES6 a le mot-clé « class » et nous devrions tous l'utiliser

Non.

Il y a des tas de raisons valables pour éviter le mot-clé ES6 class, et non des moindres, ce qui en fait un ajustement gênant pour JavaScript.

Nous avons déjà un incroyablement puissant et expressif système d'objets en JavaScript. Le concept de classe tel qu'il est implémenté aujourd'hui en JavaScript est plus restrictif (dans le sens mauvais, pas le sens typage fort), et il obscurcit l'excellent système de prototypes objetqui a été conçu pour le langage il y a bien longtemps.

Vous savez ce qui serait vraiment bien pour JavaScript ? Une meilleure syntaxe et des abstractions conçues par-dessus les prototypes de la perspective d'un programmeur familier à la programmation objet orientée prototype.

Ce serait vraiment cool.

XII - Remerciements

Merci à Eric Elliott d'avoir accepté que l'on publie une traduction de son article. Eric Elliott est l'auteur de « Programming JavaScript Applications » (O'Reilly), invité au film documentaire en production « Programming Literacy ». Il a contribué à l'expérience logicielle pour Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, et pour des artistes comme Usher, Frank Ocean ou Metallica. Vous pouvez trouver ses autres publications sur son blog : ericleads.com.

Je remercie également Claude LELOUP et f-leb pour la correction orthographique et vermine pour les conseils de mise en forme.

Les opinions exposées dans cet article n'engagent que l'auteur.