[Problème 780] Vous ne comprenez pas la classe JS: ES6

image

Préface

Il y a longtemps que vous ne voyez pas, vous ne comprenez pas la série JS et je vous revois. L'article d'aujourd'hui est le dernier article de celui-ci et des prototypes d'objets. Je continuerai à partager la traduction et le partage apportés par l'auteur de la colonne de lecture du matin @JoeHetfield.


Le texte commence ici ~


S'il y a un message clé dans la seconde moitié de ce livre (Chapitres 4 à 6), c'est que les classes sont un modèle de conception optionnel pour le code (pas nécessaire), et elles sont utilisées dans les langages [[Prototype]] comme JavaScript. Il est toujours difficile de le mettre en œuvre.


Bien qu'une grande partie de cet embarras concerne la grammaire, elle ne se limite pas à cela. Les chapitres 4 et 5 examinent beaucoup de syntaxe laide, des références .prototype encombrantes qui gâchent le code, au polymorphisme hypothétique explicite: lorsque vous donnez aux méthodes le même nom à différents niveaux de la chaîne pour essayer d'atteindre Références polymorphes des méthodes aux méthodes de haut niveau. .constructor a été incorrectement interprété comme "construit par XX", ce qui est devenu une définition peu fiable et une autre syntaxe laide.


Mais la question de la conception de la classe est beaucoup plus profonde. Le chapitre 4 souligne que dans les langages traditionnels orientés classes, la classe a en fait une action de copie de la classe parent vers la classe enfant, et de la classe enfant vers l'instance. Dans [[Prototype]], l'action n'est pas une copie, mais le contraire -Un lien de commission.


La délégation de style et de comportement OLOO accepte [[Prototype]] au lieu de le cacher. En comparant leur simplicité, le problème des classes dans JS est mis en évidence.


classe

Nous n'avons plus à discuter de ces problèmes. Je suis ici simplement pour réitérer ces questions juste pour les garder fraîches dans votre esprit, afin que nous puissions porter notre attention sur le mécanisme de classe ES6. Nous allons montrer comment cela fonctionne ici, et voir si la classe résout réellement l'un de ces problèmes de «classe».


Revenons à l'exemple Widget / Button du chapitre 6:

image.png

En plus d'avoir une meilleure syntaxe, que résout ES6 d'autre?


Plus de références (dans un sens, continuez à lire!) À .prototype pour gâcher le code.

Button est déclaré comme directement "hérité de" (c'est-à-dire étend) Widget, au lieu de remplacer les objets liés par .prototype par Object.create (..), ou d'utiliser __proto__ et Object.setPrototypeOf (..) pour définir il.


super (..) nous donne maintenant une capacité très utile d'être relativement polymorphe, de sorte que toute méthode à un certain niveau de la chaîne peut faire référence à la méthode du même nom au niveau supérieur de la chaîne. Dans le chapitre 4, il y a un phénomène étrange à propos des constructeurs: les constructeurs n'appartiennent pas à leurs classes, et n'ont donc aucun lien avec les classes. super (..) contient une solution à ce problème-super () fonctionnera comme prévu à l'intérieur du constructeur.

La syntaxe littérale de classe ne donne pas beaucoup d'inspiration pour spécifier des attributs (uniquement pour les méthodes). Cela semble limiter quelque chose, mais dans la plupart des cas, on s'attend à ce qu'un attribut (état) existe en dehors de l '"instance" à la fin de la chaîne. C'est généralement une erreur et surprenante (car cet état est implicitement «Partager» dans toutes les «instances»). Ainsi, on peut aussi dire que la syntaxe de classe vous empêche de faire des erreurs.


extend vous permet même d'étendre les (sous) types d'objets intégrés, tels que Array ou RegExp, d'une manière très naturelle. Faire cela sans class .. extend a toujours été une tâche extrêmement complexe et frustrante, et seuls les auteurs de framework les plus expérimentés ont résolu ce problème correctement. Maintenant, c'est du gâteau!


En toute honnêteté, pour les problèmes les plus évidents (syntaxiques) et les endroits surprenants du code de style prototype classique, ce sont en effet des solutions substantielles.


fosse de classe

Cependant, ce ne sont pas tous des avantages. L'utilisation de "classe" comme modèle de conception dans JS pose encore des problèmes profonds et très ennuyeux.


Tout d'abord, la syntaxe de classe peut vous convaincre que JS a un nouveau mécanisme de «classe» dans ES6. Mais non. La classe n'est en grande partie qu'un sucre syntaxique pour le mécanisme existant [[Prototype]] (délégation)!


Cela signifie que la classe n'est en fait pas copiée statiquement au moment de la déclaration comme les langages orientés classes traditionnels. Si vous modifiez / remplacez une méthode sur la "classe parente" (intentionnellement ou non), la "classe" enfant et / ou l'instance seront "affectées" car elles n'ont pas reçu de copie au moment de la déclaration, elles le sont toujours Utilisez le modèle de délégation en temps réel basé sur [[Prototype]].

image


Ce comportement ne semble raisonnable que si vous connaissez déjà la nature du délégué, au lieu de vous attendre à copier à partir de la «classe réelle». La question que vous devez vous poser est la suivante: pourquoi choisissez-vous la syntaxe de classe pour quelque chose de fondamentalement différent des classes?


La syntaxe de classe d'ES6 ne rend-elle pas plus difficile d'observer et de comprendre la différence entre les classes traditionnelles et les objets délégués?


La syntaxe de classe ne permet pas de déclarer les membres d'attribut de la classe (uniquement pour les méthodes). Donc, si vous avez besoin de suivre l'état partagé entre les objets, vous reviendrez à la vilaine syntaxe .prototype, comme ceci:

image


Le plus gros problème ici est que parce qu'il expose (des fuites!) .Prototype comme détail d'implémentation, il trahit l'intention originale de la syntaxe de classe.


De plus, nous sommes toujours confrontés au piège surprenant: this.count ++ créera implicitement une propriété d'obscurcissement séparée .count sur les deux objets c1 et c2, au lieu de mettre à jour l'état partagé. La classe ne nous donne aucun réconfort sur cette question, sauf (vraisemblablement) par le manque de support grammatical pour impliquer que vous ne devriez pas le faire du tout.


De plus, la couverture involontaire est toujours un désastre:

image.png

Il y a aussi des questions subtiles sur le fonctionnement de Super. Vous pouvez supposer que super sera lié d'une manière similaire à ceci (chapitre 2), c'est-à-dire que super sera toujours lié à la position de la méthode courante dans la chaîne [[Prototype]] Niveau supérieur.


Cependant, en raison de problèmes de performances (cette liaison est déjà très gourmande en performances), super n'est pas lié dynamiquement. Il est lié quelque peu «statiquement» lorsqu'il est déclaré. Ce n’est pas grave, non?


Hmm ... ça l'est peut-être, peut-être pas Si vous, comme la plupart des développeurs JS, commencez à attribuer des fonctions à différents objets (à partir des définitions de classe), de différentes manières, vous ne réaliserez peut-être pas que dans tous ces cas, le super mécanisme sous-jacent J'ai dû se relier à chaque fois.


Et en fonction de la syntaxe que vous utilisez pour chaque affectation, il est très probable que dans certains cas, super ne puisse pas être lié correctement (du moins pas comme prévu), vous pouvez donc (au moment de la rédaction, TC39 discute Cette question) devra utiliser toMethod (..) pour lier manuellement super (un peu comme vous devez le lier avec bind (..) - voir chapitre 2).


Vous aviez l'habitude d'affecter des méthodes à différents objets pour tirer automatiquement parti de la dynamique de cela grâce à des règles de liaison implicites (voir chapitre 2). Mais pour la méthode utilisant super, la même chose ne se produira probablement pas.


Considérez comment super devrait agir ici (pour D et E):

image


Si vous pensez (très raisonnable!) Que super sera automatiquement lié lors de son appel, vous pouvez vous attendre à ce que super () reconnaisse automatiquement que E délègue à D, donc E.foo () utilisant super () devrait être appelé D.foo ().


Ce n'est pas comme ça. Pour des raisons de performance de pragmatisme, super ne retarde pas la liaison (c'est-à-dire la liaison dynamique) de cette manière. Au lieu de cela, il est dérivé de [[HomeObject]]. [[Prototype]] lorsqu'il est appelé, et [[HomeObject]] est en fait lié statiquement lorsqu'il est déclaré.


Dans cet exemple particulier, super () se résout toujours en P.foo () car la méthode [[HomeObject]] est toujours C et C. [[Prototype]] est P.


Il peut y avoir des moyens de résoudre manuellement ces pièges. Dans ce scénario, l'utilisation de toMethod (..) pour lier / relier la méthode [[HomeObject]] (définir l'objet [[Prototype]] ensemble!) Semble fonctionner:

image

Remarque: toMethod () clone cette méthode, puis prend son premier paramètre comme homeObject (c'est pourquoi nous passons en E), et le deuxième paramètre (optionnel) est utilisé pour définir le nom de la nouvelle méthode (ne pas garder "foo" changement).


En plus de ce scénario, il reste à voir s'il existe d'autres situations extrêmes qui vont piéger les développeurs. Dans tous les cas, vous devrez vous donner la peine de rester éveillé: là où le moteur détermine automatiquement le super pour vous, et où vous devez le gérer manuellement. Oh!


Le statique est-il meilleur que dynamique?

Mais le plus gros problème avec ES6 est que tous ces pièges signifient que la classe vous amène dans une grammaire, ce qui semble impliquer (comme une classe traditionnelle) une fois que vous déclarez une classe, c'est une définition statique de quelque chose (Il sera instancié dans le futur). Vous oubliez complètement le fait que C est un objet, une chose concrète avec laquelle vous pouvez interagir directement.


Dans les langages traditionnels orientés classes, vous n'ajustez jamais la définition d'une classe ultérieurement, de sorte que le modèle de conception de classe ne fournit pas de telles fonctionnalités. Mais l'un des éléments les plus puissants de JS est qu'il est dynamique et que la définition de tout objet est (à moins que vous ne le rendiez immuable) quelque chose qui n'est ni fixe ni modifiable.


La classe semble impliquer que vous ne devriez pas faire une telle chose, en vous obligeant à utiliser la syntaxe .prototype, ou en vous forçant à considérer les pièges de super, etc. Et il ne fournit presque aucun support pour tous les écueils que ce mécanisme dynamique peut entraîner.


En d'autres termes, la classe semble vous dire: "La dynamique est trop mauvaise, donc ce n'est peut-être pas une bonne idée. Voici ce qui semble être une syntaxe statique, encodez statiquement vos trucs."


Quel triste commentaire à propos de JavaScript: la dynamique est trop difficile, faisons semblant d'être (mais pas vraiment!) Statique.


C'est pourquoi la classe d'ES6 prétend être une solution grammaticale aux maux de tête, mais elle rend l'eau plus trouble et il est plus difficile de se faire une compréhension claire et concise de JS.


Remarque: Si vous utilisez l'outil .bind (..) pour créer une fonction liée (voir le chapitre 2), cette fonction ne peut pas être étendue avec ES6 extend comme les fonctions ordinaires.


la revue

class fait un bon travail en prétendant résoudre le problème du modèle de conception de classe / héritage dans JS. Mais il a en fait fait le contraire: cela cache de nombreux problèmes et introduit d'autres choses subtiles et dangereuses.


Class a apporté de nouvelles contributions au problème de «classe» qui a torturé le langage JavaScript pendant près de 20 ans. À certains égards, il pose plus de questions qu'il n'en résout, et en plus de l'élégance et de la simplicité du mécanisme [[Prototype]], cela ressemble à un match très artificiel dans l'ensemble.


Bottom line: si la classe ES6 rend difficile l'utilisation de [[Prototype]] de manière robuste, et cache la propriété la plus importante du mécanisme d'objet JS - lien de délégation en temps réel entre les objets - nous ne devons pas penser que le problème causé par la classe est plus difficile qu'il ne résout Plus, et le minimiser comme un anti-modèle?


Je ne peux vraiment pas vous aider à répondre à cette question. Mais j'espère que ce livre a exploré cette question en profondeur que vous n'avez jamais expérimentée auparavant et vous a donné les informations dont vous avez besoin pour répondre vous-même à cette question.


Enfin, bien qu'il s'agisse du dernier article de ceci et des prototypes d'objets, vous aurez peut-être besoin des articles suivants pour mieux vous connecter:

[Problème 773] Vous ne connaissez pas JS: délégation de comportement

[Problème 772] Vous ne comprenez pas JS: Prototype

[Problème 767] Vous ne comprenez pas JS: mélanger (obscurcir) des objets de "classe"


Ou, répondez [vous ne comprenez pas js] et consultez la colonne @ JoeHetfield .


Je suppose que tu aimes

Origine blog.51cto.com/15080028/2595037
conseillé
Classement