Les prototypes sont une optimisation .
Un bon exemple de leur bonne utilisation est la bibliothèque jQuery. Chaque fois que vous obtenez un objet jQuery en utilisant $('.someClass')
, cet objet a des dizaines de «méthodes». La bibliothèque pourrait y parvenir en renvoyant un objet:
return {
show: function() { ... },
hide: function() { ... },
css: function() { ... },
animate: function() { ... },
// etc...
};
Mais cela signifierait que chaque objet jQuery en mémoire aurait des dizaines d'emplacements nommés contenant les mêmes méthodes, encore et encore.
Au lieu de cela, ces méthodes sont définies sur un prototype et tous les objets jQuery "héritent" de ce prototype afin d'obtenir toutes ces méthodes à un coût d'exécution très faible.
Une partie vitale de la façon dont jQuery fait les choses correctement est que cela est caché au programmeur. Il est traité uniquement comme une optimisation, pas comme quelque chose dont vous devez vous soucier lors de l'utilisation de la bibliothèque.
Le problème avec JavaScript est que les fonctions de constructeur nues nécessitent que l'appelant se souvienne de les préfixer, new
sinon elles ne fonctionnent généralement pas. Il n'y a aucune bonne raison à cela. jQuery fait les choses correctement en cachant ce non-sens derrière une fonction ordinaire $
, vous n'avez donc pas à vous soucier de la façon dont les objets sont implémentés.
Afin que vous puissiez facilement créer un objet avec un prototype spécifié, ECMAScript 5 comprend une fonction standard Object.create
. Une version grandement simplifiée de celui-ci ressemblerait à ceci:
Object.create = function(prototype) {
var Type = function () {};
Type.prototype = prototype;
return new Type();
};
Il s'occupe simplement de la douleur d'écrire une fonction constructeur et de l'appeler ensuite avec new
.
Quand éviterais-tu les prototypes?
Une comparaison utile est avec les langages OO populaires tels que Java et C #. Ceux-ci prennent en charge deux types d'héritage:
- Interface héritage, où vous
implement
une interface
telle que la classe fournit sa propre implémentation unique pour tous les membres de l'interface.
- la mise en œuvre d' héritage, où vous
extend
un class
qui fournit des implémentations par défaut de certaines méthodes.
En JavaScript, l'héritage prototypique est une sorte d' héritage d' implémentation . Donc, dans les situations où (en C # ou Java) vous auriez dérivé d'une classe de base pour obtenir le comportement par défaut, auquel vous apportez ensuite de petites modifications via des remplacements, alors en JavaScript, l'héritage prototypique a du sens.
Cependant, si vous êtes dans une situation où vous auriez utilisé des interfaces en C # ou Java, vous n'avez besoin d'aucune fonctionnalité de langage particulière dans JavaScript. Il n'est pas nécessaire de déclarer explicitement quelque chose qui représente l'interface, et pas besoin de marquer les objets comme "implémentant" cette interface:
var duck = {
quack: function() { ... }
};
duck.quack(); // we're satisfied it's a duck!
En d'autres termes, si chaque "type" d'objet a ses propres définitions des "méthodes", alors il n'y a aucune valeur à hériter d'un prototype. Après cela, cela dépend du nombre d'instances que vous allouez de chaque type. Mais dans de nombreuses conceptions modulaires, il n'y a qu'une seule instance d'un type donné.
Et en fait, il a été suggéré par de nombreuses personnes que l'héritage de mise en œuvre est mauvais . Autrement dit, s'il y a des opérations courantes pour un type, alors il est peut-être plus clair si elles ne sont pas placées dans une classe de base / super, mais sont simplement exposées comme des fonctions ordinaires dans un module, auquel vous passez le ou les objets vous voulez qu'ils opèrent.