Javascript quand utiliser des prototypes

93

J'aimerais comprendre quand il est approprié d'utiliser des méthodes prototypes dans js. Devraient-ils toujours être utilisés? Ou y a-t-il des cas où leur utilisation n'est pas préférée et / ou entraîne une pénalité de performance?

En cherchant sur ce site sur les méthodes courantes d'espacement de noms dans js, il semble que la plupart utilisent une implémentation non basée sur un prototype: utiliser simplement un objet ou un objet fonction pour encapsuler un espace de noms.

Venant d'un langage basé sur les classes, il est difficile de ne pas essayer de faire des parallèles et de penser que les prototypes sont comme des «classes» et que les implémentations d'espace de noms que j'ai mentionnées sont comme des méthodes statiques.

opl
la source

Réponses:

133

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, newsinon 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 implementune interfacetelle 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 extendun classqui 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.

Daniel Earwicker
la source
1
Bonne explication. Alors seriez-vous d'accord pour dire que, puisque vous considérez les prototypes comme une optimisation, ils peuvent toujours être utilisés pour améliorer votre code? Je me demande s'il y a des cas où l'utilisation de prototypes n'a pas de sens ou entraîne une pénalité de performance.
opl
Dans votre suivi, vous mentionnez que "cela dépend du nombre d'instances que vous allouez de chaque type". Mais l'exemple auquel vous faites référence n'utilise pas de prototypes. Où est la notion d'allocation d'une instance (utiliseriez-vous toujours "new" ici)? Aussi: disons que la méthode charlatan avait un paramètre - chaque invocation de duck.quack (param) entraînerait-elle la création d'un nouvel objet en mémoire (peut-être n'est-il pas pertinent s'il a un paramètre ou non)?
opl
3
1. Je voulais dire que s'il y avait un grand nombre d'instances d'un type de canard, alors il serait logique de modifier l'exemple pour que la quackfonction soit dans un prototype, auquel les nombreuses instances de canard sont liées. 2. La syntaxe du littéral objet { ... }crée une instance (il n'est pas nécessaire de l'utiliser newavec elle). 3. L' appel de n'importe quelle fonction JS provoque la création d'au moins un objet en mémoire - il est appelé l' argumentsobjet et stocke les arguments passés dans l'appel: developer.mozilla.org/en/JavaScript/Reference/...
Daniel Earwicker
Merci, j'ai accepté votre réponse. Mais j'ai encore une légère confusion avec votre point (1): je ne saisis pas ce que vous entendez par "grand nombre d'instances d'un type de canard". Comme vous l'avez dit dans (3) chaque fois que vous appelez une fonction JS, un objet est créé en mémoire - donc même si vous n'avez qu'un seul type de canard, n'alloueriez-vous pas de mémoire à chaque fois que vous appelez une fonction de canard (dans quel cas il serait toujours judicieux d'utiliser un prototype)?
opl
11
+1 La comparaison avec jQuery a été la première explication claire et concise de quand et pourquoi utiliser des prototypes que j'ai lus. Merci beaucoup.
GFoley83
46

Vous devez utiliser des prototypes si vous souhaitez déclarer une méthode "non statique" de l'objet.

var myObject = function () {

};

myObject.prototype.getA = function (){
  alert("A");
};

myObject.getB = function (){
  alert("B");
};

myObject.getB();  // This works fine

myObject.getA();  // Error!

var myPrototypeCopy = new myObject();
myPrototypeCopy.getA();  // This works, too.
KeatsKelleher
la source
@keatsKelleher mais nous pouvons créer une méthode non statique pour l'objet en définissant simplement la méthode à l'intérieur de la fonction constructeur en utilisant l' thisexemple, this.getA = function(){alert("A")}n'est-ce pas?
Amr Labib
17

Une des raisons d'utiliser l' prototypeobjet intégré est que vous dupliquiez plusieurs fois un objet qui partagera des fonctionnalités communes. En attachant des méthodes au prototype, vous pouvez économiser sur la duplication des méthodes créées pour chaque newinstance. Mais lorsque vous attachez une méthode au prototype, toutes les instances auront accès à ces méthodes.

Disons que vous avez une Car()classe / objet de base .

function Car() {
    // do some car stuff
}

puis vous créez plusieurs Car()instances.

var volvo = new Car(),
    saab = new Car();

Maintenant, vous savez chaque voiture devra conduire, allumer, etc. Au lieu de fixer une méthode directement à la Car()classe (qui prend la mémoire par chaque instance créée), vous pouvez joindre les méthodes au prototype au lieu (créer les méthodes que once), donnant ainsi accès à ces méthodes à la fois au nouveau volvoet au saab.

// just mapping for less typing
Car.fn = Car.prototype;

Car.fn.drive = function () {
    console.log("they see me rollin'");
};
Car.fn.honk = function () {
    console.log("HONK!!!");
}

volvo.honk();
// => HONK!!!
saab.drive();
// => they see me rollin'
hellatan
la source
2
en fait, c'est incorrect. volvo.honk () ne fonctionnera pas car vous avez complètement remplacé l'objet prototype, pas étendu. Si vous deviez faire quelque chose comme ça, cela fonctionnerait comme prévu: Car.prototype.honk = function () {console.log ('HONK');} volvo.honk (); // 'HONK'
29er
1
@ 29er - de la manière dont j'ai écrit cet exemple, vous avez raison. L'ordre compte. Si je Car.prototype = { ... }devais garder cet exemple tel new Car()quel , le devrait venir avant d'appeler a comme illustré dans ce jsfiddle: jsfiddle.net/mxacA . Quant à votre argument, ce serait la bonne façon de le faire: jsfiddle.net/Embnp . Le plus drôle est que je ne me souviens pas avoir répondu à cette question =)
hellatan
@hellatan, vous pouvez résoudre ce problème en définissant constructor: Car sur puisque vous avez remplacé la propriété prototype par un littéral d'objet.
Josh Bedo
@josh merci de l'avoir signalé. J'ai mis à jour ma réponse pour ne pas écraser le prototype par un objet littéral, comme cela aurait dû l'être depuis le début.
hellatan
12

Mettez des fonctions sur un objet prototype lorsque vous allez créer de nombreuses copies d'un type d'objet particulier et qu'elles doivent toutes partager des comportements communs. Ce faisant, vous économiserez de la mémoire en n'ayant qu'une seule copie de chaque fonction, mais ce n'est que l'avantage le plus simple.

Changer de méthode sur des objets prototypes, ou ajouter des méthodes, change instantanément la nature de toutes les instances du ou des types correspondants.

Maintenant, la raison exacte pour laquelle vous feriez toutes ces choses dépend principalement de la conception de votre propre application et du genre de choses que vous devez faire dans le code côté client. (Une toute autre histoire serait celle du code à l'intérieur d'un serveur; il est beaucoup plus facile d'imaginer y faire du code "OO" à plus grande échelle.)

Pointu
la source
donc quand j'instancie un nouvel objet avec des méthodes prototypes (via un nouveau mot-clé), alors cet objet n'obtient pas une nouvelle copie de chaque fonction (juste une sorte de pointeur)? Si tel est le cas, pourquoi ne voudriez-vous pas utiliser un prototype?
opl
comme @marcel, d'oh ... =)
hellatan
@opi oui, vous avez raison - il n'y a pas de copie faite. Au lieu de cela, les symboles (noms de propriété) sur l'objet prototype sont simplement "là" comme des parties virtuelles de chaque objet d'instance. La seule raison pour laquelle les gens ne voudraient pas se soucier de cela serait les cas où les objets sont de courte durée et distincts, ou où il n'y a pas beaucoup de «comportement» à partager.
Pointy le
3

Si j'explique en terme basé sur la classe, alors Person est une classe, walk () est la méthode Prototype. Ainsi, walk () n'aura son existence qu'après avoir instancié un nouvel objet avec ceci.

Donc, si vous voulez créer des copies d'objet comme Person, vous pouvez créer de nombreux utilisateurs Prototype est une bonne solution car il économise de la mémoire en partageant / héritant de la même copie de fonction pour chacun des objets en mémoire.

Alors que la statique n'est pas très utile dans un tel scénario.

function Person(){
this.name = "anonymous";
}

// its instance method and can access objects data data 
Person.prototype.walk = function(){
alert("person has started walking.");
}
// its like static method
Person.ProcessPerson = function(Person p){
alert("Persons name is = " + p.name);
}

var userOne = new Person();
var userTwo = new Person();

//Call instance methods
userOne.walk();

//Call static methods
Person.ProcessPerson(userTwo);

Donc, avec cela, c'est plus une méthode d'instance. L'approche de l'objet est comme les méthodes statiques.

https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript

Anil Namde
la source