Les inconvénients de l'héritage de prototype JavaScript, quels sont-ils?

J'ai récemment regardé les présentations de JavaScript de Douglas Crockford , où il rave au sujet de l'héritage de prototype JavaScript comme si c'était la meilleure chose depuis le pain blanc tranché. Compte tenu de la réputation de Crockford, ça va très bien.

Quelqu'un peut-il me dire quel est l'inconvénient de l'héritage de prototype JavaScript? (Par rapport à l'héritage de classe en C # ou Java, par exemple)

Les choses que je manque lors de la sous-classe d'un objet existant dans JavaScript vs hériter d'une classe en C ++:

  1. Aucune manière standard (intégrée à la langue) de l'écrire qui ressemble à la même chose quel développeur l'a écrit.
  2. L'écriture de votre code ne génère pas naturellement une définition d'interface de la manière dont le fichier d'en-tête de classe est en C ++.
  3. Il n'existe aucun moyen standard de faire des variables ou des méthodes protégées et privées. Il existe certaines conventions pour certaines choses, mais encore, les différents développeurs le font différemment.
  4. Il n'y a pas d'étape de compilation pour vous dire quand vous avez fait des erreurs de frappe fausses dans votre définition.
  5. Il n'y a pas de type de sécurité lorsque vous le souhaitez.

Ne me méprenez pas, il y a un zillion des avantages de la façon dont l'héritage prototype javascript fonctionne contre C ++, mais ce sont quelques-uns des endroits où je trouve que javascript fonctionne moins en douceur.

4 et 5 ne sont pas strictement liés à l'héritage de prototype, mais ils entrent en jeu lorsque vous avez un projet de taille importante avec de nombreux modules, de nombreuses classes et de nombreux fichiers et vous souhaitez refactoriser certaines classes. En C ++, vous pouvez modifier les classes, changer autant d'appelants que vous pouvez trouver, puis laisser le compilateur trouver toutes les références restantes pour vous qui doivent être réparées. Si vous avez ajouté des paramètres, des types modifiés, des noms de méthodes modifiés, des méthodes déplacées, etc. Le compilateur vous montrera si vous deviez réparer les choses.

En Javascript, il n'y a pas de moyen facile de découvrir tous les morceaux possibles de code qui doivent être modifiés sans exécuter littéralement tous les chemins de code possibles pour voir si vous avez manqué quelque chose ou fait des erreurs de frappe. Bien qu'il s'agisse d'un désavantage général de javascript, je l'ai trouvé particulièrement entré en jeu lors du refactorisation des classes existantes dans un projet de taille importante. Je suis arrivé à la fin d'un cycle de publication dans un projet JS de taille importante et j'ai décidé que je ne devrais PAS faire de refactorisation pour résoudre un problème (même si c'était la meilleure solution) parce que le risque de ne pas trouver toutes les ramifications possibles de Ce changement était beaucoup plus élevé dans JS que C ++.

Donc, par conséquent, je trouve qu'il est plus risqué de faire des changements liés à OO dans un projet JS.

Selon mon expérience, un inconvénient majeur est que vous ne pouvez pas imiter les variables «privées» des membres de Java en encapsulant une variable dans une fermeture, mais qu'elles sont toujours accessibles aux méthodes ajoutées ultérieurement au prototype.

c'est à dire:

function MyObject() { var foo = 1; this.bar = 2; } MyObject.prototype.getFoo = function() { // can't access "foo" here! } MyObject.prototype.getBar = function() { return this.bar; // OK! } 

Cela confond les programmeurs OO qui apprennent à rendre les variables membres privées.

Je pense que le principal danger est que plusieurs parties peuvent se passer des méthodes de prototypes mutuelles, entraînant des comportements inattendus.

Ceci est particulièrement dangereux car tant de programmeurs sont enthousiasmés par le "héritage" du prototype (je l'appellerais extension ) et donc commencer à l'utiliser partout, en ajoutant des méthodes à gauche et à droite qui peuvent avoir un comportement ambigu ou subjectif. En fin de compte, si elle n'est pas contrôlée, ce type de "prototype de prolifération de méthode" peut conduire à un code très difficile à conserver.

Un exemple populaire serait la méthode de trim . Il pourrait être mis en œuvre quelque chose comme ça par une partie:

 String.prototype.trim = function() { // remove all ' ' characters from left & right } 

Ensuite, un autre parti peut créer une nouvelle définition, avec une signature complètement différente , en prenant un argument qui spécifie le caractère à couper. Tout à coup, tout le code qui ne transmet rien à la trim n'a aucun effet.

Ou une autre partie réimplique la méthode pour supprimer ' ' caractères ' ' et les autres formes d'espace blanc (p. Ex., Les onglets, les sauts de ligne). Cela pourrait passer inaperçu pendant un certain temps, mais entraîner des comportements étranges sur la route.

Selon le projet, ces risques peuvent être considérés comme des dangers à distance. Mais ils peuvent se produire, et de ma compréhension, c'est pourquoi les bibliothèques telles que Underscore.js choisissent de garder toutes leurs méthodes dans les espaces de noms plutôt que d'ajouter des méthodes de prototypes.

( Mise à jour : Évidemment, il s'agit d'un appel de jugement. D'autres bibliothèques – à savoir, le Prototype convenablement nommé – vont sur la route du prototype. Je n'essaie pas de dire que l'une des voies est la bonne ou la mauvaise, seulement que c'est l'argument J'ai entendu contre l'utilisation de méthodes prototypes aussi généreusement.)

Je manque d'être en mesure de séparer l'interface de la mise en œuvre. Dans les langues avec un système d'héritage qui inclut des concepts comme l' abstract ou l' interface , vous pouvez par exemple déclarer votre interface dans votre couche de domaine mais mettre l'implémentation dans votre couche d'infrastructure. ( Architecture Cf. onion ). Le système de héritage de JavaScript n'a aucun moyen de faire quelque chose comme ça.

J'aimerais savoir si ma réponse intuitive correspond à ce que pensent les experts.

Ce qui me préoccupe, c'est que si j'ai une fonction dans C # (par souci de discussion) qui prend un paramètre, tout développeur qui écrit un code qui appelle ma fonction sait immédiatement de la signature de la fonction quel type de paramètres il faut et quel type de valeur Il revient.

Avec JavaScript "duck-typing", quelqu'un pourrait hériter un de mes objets et modifier ses fonctions et valeurs de membre (Oui, je sais que les fonctions sont des valeurs en JavaScript) de manière presque impossible, de sorte que l'objet qu'elles transmettent à ma fonction soit Pas de ressemblance avec l'objet, j'attends que ma fonction soit passée.

Je pense qu'il n'y a pas de bon moyen de rendre évident comment une fonction est censée être appelée.