Confus sur l'héritage prototypique de JavaScript avec les constructeurs

J'ai lu des pages et des pages sur l'héritage prototypique de JavaScript, mais je n'ai trouvé rien qui aborde l'utilisation de constructeurs impliquant la validation. J'ai réussi à faire fonctionner ce constructeur, mais je sais que ce n'est pas idéal, c'est-à-dire qu'il ne profite pas de l'héritage prototypique:

function Card(value) { if (!isNumber(value)) { value = Math.floor(Math.random() * 14) + 2; } this.value = value; } var card1 = new Card(); var card2 = new Card(); var card3 = new Card(); 

Il en résulte trois objets de carte avec des valeurs aléatoires. Cependant, la façon dont je comprends est que chaque fois que je crée un nouvel objet de carte de cette façon, il copie le code constructeur. Je devrais plutôt utiliser l'héritage prototypique, mais cela ne fonctionne pas:

 function Card(value) { this.value = value; } Object.defineProperty( Card, "value", { set: function (value) { if (!isNumber(value)) { value = Math.floor(Math.random() * 14) + 2; } this.value = value; } }); 

Cela ne fonctionne pas non plus:

 Card.prototype.setValue = function (value) { if (!isNumber(value)) { value = Math.floor(Math.random() * 14) + 2; } this.value = value; }; 

D'une part, je ne peux plus appeler une new Card() . Au lieu de cela, je dois appeler var card1 = new Card(); card1.setValue(); var card1 = new Card(); card1.setValue(); Cela me semble très inefficace et moche. Mais le vrai problème est qu'il définit la propriété de valeur de chaque objet Card à la même valeur. Aidez-moi!


modifier

Par la suggestion de Bergi, j'ai modifié le code comme suit:

 function Card(value) { this.setValue(value); } Card.prototype.setValue = function (value) { if (!isNumber(value)) { value = Math.floor(Math.random() * 14) + 2; } this.value = value; }; var card1 = new Card(); var card2 = new Card(); var card3 = new Card(); 

Il en résulte trois objets de carte avec des valeurs aléatoires, ce qui est génial, et je peux appeler la méthode setValue plus tard. Il ne semble pas transférer quand j'essaie d'étendre la classe cependant:

 function SpecialCard(suit, value) { Card.call(this, value); this.suit = suit; } var specialCard1 = new SpecialCard("Club"); var specialCard2 = new SpecialCard("Diamond"); var specialCard3 = new SpecialCard("Spade"); 

Je reçois l'erreur this.setValue is not a function maintenant.


Modifier 2

Cela semble fonctionner:

 function SpecialCard(suit, value) { Card.call(this, value); this.suit = suit; } SpecialCard.prototype = Object.create(Card.prototype); SpecialCard.prototype.constructor = SpecialCard; 

Est-ce une bonne façon de le faire?


Final Edit!

Merci à Bergi et à Norguard, j'ai finalement mis sur cette mise en œuvre:

 function Card(value) { this.setValue = function (val) { if (!isNumber(val)) { val = Math.floor(Math.random() * 14) + 2; } this.value = val; }; this.setValue(value); } function SpecialCard(suit, value) { Card.call(this, value); this.suit = suit; } 

Bergi m'a aidé à identifier pourquoi je ne pouvais pas hériter de la chaîne de prototypes, et Norguard expliquait pourquoi il vaut mieux ne pas se contenter de la chaîne de prototypes. J'aime cette approche parce que le code est plus propre et plus facile à comprendre.

La façon dont je comprends bien est que chaque fois que je crée un nouvel objet de carte de cette façon, il copie le code constructeur

Non, il l'exécute. Aucun problème, et votre constructeur fonctionne parfait – c'est comme ça qu'il devrait ressembler.

Les problèmes ne se poseront que lorsque vous créez des valeurs. Chaque invocation d'une fonction crée son propre ensemble de valeurs, par exemple variables privées (vous n'en avez aucune). Ils génèrent habituellement des ordures collectées, sauf si vous créez une autre valeur spéciale, une méthode privilégiée , qui est une fonction exposée qui contient une référence à la portée dans laquelle elle habite. Et oui, chaque objet a sa propre «copie» de telles fonctions, qui est Pourquoi devez-vous pousser tout ce qui n'accédera pas aux variables privées au prototype.

  Object.defineProperty( Card, "value", ... 

Attends, non. Vous définissez ici une propriété sur le constructeur, la Card fonction. Ce n'est pas ce que tu veux. Vous pouvez appeler ce code sur les instances, oui, mais notez que lors de l'évaluation de this.value = value; Il s'appellerait lui-même récursivement.

  Card.prototype.setValue = function(){ ... } 

Cela semble bon. Vous pourriez avoir besoin de cette méthode sur les objets de la Card lorsque vous allez utiliser le code de validation plus tard, par exemple lors de la modification de la valeur d'une instance de Card (je ne le pense pas, mais je ne sais pas?).

Mais je ne peux plus appeler une nouvelle carte ()

Oh, sûrement, vous pouvez. La méthode est héritée par toutes les instances de la Card , et cela comprend celui sur lequel le constructeur est appliqué ( this ). Vous pouvez facilement l'appeler à partir de là, alors déclarez votre constructeur comme ceci:

 function Card(val) { this.setValue(val); } Card.prototype... 

Il ne semble pas transférer quand j'essaie d'étendre la classe cependant.

Oui, ça ne l'est pas. L'appel de la fonction constructeur ne configure pas la chaîne prototype. Avec le new mot – clé, l'objet avec son héritage est instancié, puis le constructeur est appliqué. Avec votre code, SpecialCard s hérite de l'objet SpecialCard.prototype (qui lui-même hérite du prototype d'objet par défaut). Maintenant, nous pourrions soit soit configurer le même objet que les cartes normales, soit laisser hériter de celui-ci.

 SpecialCard.prototype = Card.prototype; 

Donc maintenant, chaque instance hérite du même objet. Cela signifie que SpecialCard s n'aura aucune méthode spéciale (du prototype) que la Card normale s n'a pas … De plus, l' instanceof opérateur ne fonctionnera plus correctement.

Donc, il y a une meilleure solution. Laissez l'objet prototype de la Card.prototype hériter de Card.prototype ! Cela peut être fait en utilisant Object.create (non pris en charge par tous les navigateurs, vous pourriez avoir besoin d'une solution de contournement ), qui est conçu pour effectuer exactement ce travail:

 SpecialCard.prototype = Object.create(Card.prototype, { constructor: {value:SpecialCard} }); SpecialCard.prototype.specialMethod = ... // now possible 

En termes de constructeur, chaque carte possède sa propre copie unique de toute méthode définie à l'intérieur du constructeur:

 this.doStuffToMyPrivateVars = function () { }; 

ou

 var doStuffAsAPrivateFunction = function () {}; 

La raison pour laquelle ils ont leurs propres copies uniques est que seules des copies uniques des fonctions, instanciées en même temps que l'objet lui-même, auront accès aux valeurs ci-jointes.

En les mettant dans la chaîne prototype, vous:

  1. Limitez-les à une seule copie (à moins d'une suppression manuelle par instance, après la création)
  2. Supprimez la capacité de la méthode d'accéder à TOUTES les variables privées
  3. Il est vraiment facile de frustrer les amis et la famille en modifiant les méthodes / propriétés prototypes sur chaque instance, à mi-programme.

La réalité de la question est que, à moins que vous ne prévoyiez de faire un jeu qui fonctionne sur les vieilles mûres ou un ancien iPod Touch, vous ne devez pas trop vous soucier des frais généraux supplémentaires des fonctions ci-jointes.

En outre, dans la programmation JS au jour le jour, la sécurité supplémentaire des objets correctement encapsulés, plus le bénéfice supplémentaire du module / des modèles de modules de révélation et du sandbox avec des fermetures SORTIE VOUREMENT le coût d'avoir des copies redondantes des méthodes attachées aux fonctions.

En outre, si vous êtes vraiment, vraiment concerné, vous pourriez faire pour regarder les modèles Entité / Système, où les entités sont à peu près des objets de données (avec leurs propres méthodes get / set spécifiques, si la confidentialité est nécessaire) … … et chacune de ces entités d'un type particulier est enregistrée dans un système personnalisé pour cette entité / composant-type.

IE: Vous auriez une Entité de Carte pour définir chaque carte dans un jeu. Chaque carte possède un CardValueComponent , un CardWorldPositionComponent , un CardRenderableComponent , un CardClickableComponent , et cetera.

 CardWorldPositionComponent = { x : 238, y : 600 }; 

Chacun de ces composants est ensuite enregistré dans un système:

 CardWorldPositionSystem.register(this.c_worldPos); 

Chaque système contient TOUTES les méthodes qui seraient normalement exécutées sur les valeurs stockées dans le composant. Les systèmes (et non les composants) dialogueront en avant et en arrière, au besoin pour envoyer des données en va-et-vient, entre les composants partagés par la même entité (c'est-à-dire: la position / la valeur / l'image de Ace of Spade peuvent être interrogées à partir de différents systèmes de sorte que Tout le monde est à jour).

Ensuite, au lieu de mettre à jour chaque objet – traditionnellement, ce serait quelque chose comme:

 Game.Update = function (timestamp) { forEach(cards, function (card) { card.update(timestamp); }); }; Game.Draw = function (timestamp, renderer) { forEach(cards, function (card) { card.draw(renderer); }); }; 

Maintenant, c'est plus comme:

 CardValuesUpdate(); CardImagesUpdate(); CardPositionsUpdate(); RenderCardsToScreen(); 

À l'intérieur de la mise à jour traditionnelle, chaque élément prend en charge sa propre gestion de l'entrée / Mouvement / Modélisation / Spritesheet-Animation / AI / et cetera, vous mettez à jour chaque sous-système l'un après l'autre et chaque sous-système parcourt chacun Entité qui a un composant enregistré dans ce sous-système, l'une après l'autre.

Il y a donc une petite empreinte mémoire sur le nombre de fonctions uniques. Mais c'est un univers très différent en termes de réflexion sur la façon de le faire.