web-dev-qa-db-fra.com

Comprendre l'héritage prototypique en JavaScript

Je suis nouveau sur JavaScript OOP. Pouvez-vous s'il vous plaît expliquer la différence entre les blocs de code suivants? J'ai testé et les deux blocs fonctionnent. Quelle est la meilleure pratique et pourquoi?

Premier bloc:

function Car(name){
    this.Name = name;
}

Car.prototype.Drive = function(){
    console.log("My name is " + this.Name + " and I'm driving.");
}

SuperCar.prototype = new Car();
SuperCar.prototype.constructor = SuperCar;

function SuperCar(name){
    Car.call(this, name);
}

SuperCar.prototype.Fly = function(){
    console.log("My name is " + this.Name + " and I'm flying!");
}

var myCar = new Car("Car");
myCar.Drive();

var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();

Deuxième bloc:

function Car(name){
    this.Name = name;
    this.Drive = function(){ 
        console.log("My name is " + this.Name + " and I'm driving.");
    }
}

SuperCar.prototype = new Car();

function SuperCar(name){
    Car.call(this, name);
    this.Fly = function(){
        console.log("My name is " + this.Name + " and I'm flying!");
    }
}

var myCar = new Car("Car");
myCar.Drive();

var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();

Pourquoi l'auteur a-t-il ajouté les méthodes Drive et Fly à l'aide de prototype et ne les a-t-il pas déclarées comme méthode this.Drive dans la classe Car et comme this.Fly dans la classe SuperCar

Pourquoi SuperCar.prototype.constructor doit-il être remis à SuperCar? La propriété constructor est-elle remplacée lorsque prototype est défini? J'ai commenté cette ligne et rien n'a changé.

Pourquoi appeler Car.call(this, name); dans le constructeur SuperCar? Les propriétés et méthodes de Car ne seront-elles pas 'héritées' quand je le ferai

var myCar = new Car("Car");
166
Dasha Salo

Les deux blocs diffèrent de manière à ce que, dans le premier exemple, Drive() n'existe qu'une seule fois, alors que, dans la deuxième approche, Drive() existera par instance (chaque fois que vous ferez new Car(), la fonction drive() sera créée à nouveau). Ou différents ont dit le premier utilise le prototype pour stocker la fonction et le second le constructeur. La recherche de fonctions est constructeur puis prototype. Donc, pour votre recherche de Drive(), il le trouve, qu’il soit dans le constructeur ou dans le prototype. L'utilisation du prototype est plus efficace car vous n'avez généralement besoin d'une fonction qu'une fois par type.

L'appel new en javascript définit automatiquement le constructeur dans le prototype. Si vous écrasez le prototype, vous devez donc définir le constructeur manuellement.

L'héritage javascript n'a rien à voir avec super. Donc, si vous avez une sous-classe, la seule chance d'appeler le super constructeur est par son nom.

81
Norbert Hartl

Pour ajouter à la réponse de Norbert Hartl , SuperCar.prototype.constructor n'est pas nécessaire, mais certaines personnes l'utilisent comme moyen pratique d'obtenir la fonction de construction d'un objet (les objets SuperCar dans ce cas).

Juste du premier exemple, Car.call (this, name) est dans la fonction constructeur SuperCar parce que, lorsque vous faites cela:

var mySuperCar = new SuperCar("SuperCar");

C'est ce que fait JavaScript:

  1. Un nouvel objet vierge est instancié.
  2. Le prototype interne de l'objet frais est défini sur Car.
  3. La fonction constructeur SuperCar s'exécute.
  4. L'objet fini est renvoyé et défini dans mySuperCar.

Remarquez comment JavaScript n'a pas appelé Car pour vous. Les prototypes étant tels qu’ils sont, toute propriété ou méthode que vous ne définissez pas vous-même pour SuperCar sera recherchée dans Car. Parfois, c'est bon, par exemple SuperCar n'a pas de méthode Drive, mais il peut partager celle de Car. Tous les SuperCars utiliseront donc la même méthode. D'autres fois, vous ne voulez pas partager, comme chaque SuperCar ayant son propre nom. Alors, comment peut-on définir le nom de chaque SuperCar comme il se doit? Vous pouvez définir this.Name dans la fonction constructeur SuperCar:

function SuperCar(name){
    this.Name = name;
}

Cela fonctionne, mais attendez une seconde. N'avons-nous pas fait exactement la même chose dans le constructeur automobile? Je ne veux pas nous répéter. Puisque Car définit déjà le nom, appelons-le simplement.

function SuperCar(name){
    this = Car(name);
}

Oups, vous ne voulez jamais changer la référence d'objet this spéciale. Rappelez-vous les 4 étapes? Accrochez-vous à cet objet que JavaScript vous a fourni, car c’est le seul moyen de conserver le précieux lien de prototype interne entre votre objet SuperCar et la voiture. Alors, comment pouvons-nous définir Name, sans nous répéter et sans jeter notre nouvel objet SuperCar? JavaScript a consacré tant d’efforts particuliers à nous préparer?

Deux choses. Un: la signification de this est flexible. Deux: la voiture est une fonction. Il est possible d'appeler Car, non pas avec un objet instancié frais et vierge, mais plutôt avec, par exemple, un objet SuperCar. Cela nous donne la solution finale, qui fait partie du premier exemple de votre question:

function SuperCar(name){
    Car.call(this, name);
}

En tant que fonction, il est autorisé d'appeler Car avec la méthode call de la fonction , qui modifie la signification de this dans Car en l'instance SuperCar que nous construisons. Presto! Désormais, chaque SuperCar reçoit sa propre propriété Name.

Pour conclure, Car.call(this, name) dans le constructeur SuperCar attribue à chaque nouvel objet SuperCar sa propre propriété Name unique, mais sans dupliquer le code déjà présent dans Car.

Les prototypes ne sont pas effrayants une fois que vous les comprenez, mais ils ne ressemblent pas du tout au modèle classique classe/héritage OOP. J'ai écrit un article sur le concept de prototypes en JavaScript . Il est écrit pour un moteur de jeu qui utilise JavaScript, mais c'est le même moteur JavaScript que celui utilisé par Firefox, donc tout devrait être pertinent. J'espère que cela t'aides.

139
Tung Nguyen

Norbert, vous devriez noter que votre premier exemple est à peu près ce que Douglas Crockford appelle l'héritage pseudoclassique. Quelque chose à noter à ce sujet:

  1. Vous appelez le constructeur Car à deux reprises, une fois depuis la ligne SuperCar.prototype = new Car () et l’autre depuis la ligne "constructor stealing" Car.call (this ... vous pouvez créer une méthode d’aide pour hériter des prototypes et votre Le constructeur automobile ne devra exécuter qu'une seule fois, ce qui rendra la configuration plus efficace.
  2. La ligne SuperCar.prototype.constructor = SuperCar vous permettra d’utiliser instanceof pour identifier le constructeur. Certaines personnes veulent que d'autres évitent simplement d'utiliser instanceof
  3. Les références telles que: var arr = ['one', 'two'] définies sur le super (par exemple, Car) seront partagées par TOUTES les instances. Cela signifie que inst1.arr.Push ['trois'], inst2.arr.Push ['quatre'], etc., apparaîtra pour toutes les instances! Essentiellement, un comportement statique que vous ne voulez probablement pas.
  4. Votre second bloc définit la méthode fly dans le constructeur. Cela signifie qu'à chaque appel, un "objet de méthode" sera créé. Mieux vaut utiliser un prototype pour les méthodes! Vous POUVEZ cependant le conserver dans le constructeur si vous le souhaitez - vous avez juste besoin de vous protéger afin d’initialiser réellement le littéral prototype une seule fois (pseudo): if (SuperCar.prototype.myMethod! = 'Fonction') ... puis définissez votre prototype littéral.
  5. 'Pourquoi appeler Car.call (this, name) ....': Je n'ai pas le temps d'examiner attentivement votre code afin de me tromper, mais c'est généralement pour que chaque instance puisse conserver son propre état afin de résoudre le problème. problème de comportement 'staticy' du prototype de chaînage que j'ai décrit ci-dessus.

Enfin, je voudrais mentionner que j'ai plusieurs exemples de code d'héritage JavaScript de TDD qui fonctionne ici: Code et essai d'héritage de TDD JavaScript J'aimerais recevoir vos commentaires car j'espère pouvoir l'améliorer et le conserver. il open source. L'objectif est d'aider les programmeurs classiques à se familiariser rapidement avec JavaScript et à compléter l'étude des livres de Crockford et Zakas.

8
Rob
function abc() {
}

Méthodes de prototype et propriété créées pour la fonction abc

abc.prototype.testProperty = 'Hi, I am prototype property';
abc.prototype.testMethod = function() { 
   alert('Hi i am prototype method')
}

Création de nouvelles instances pour la fonction abc

var objx = new abc();

console.log(objx.testProperty); // will display Hi, I am prototype property
objx.testMethod();// alert Hi i am prototype method

var objy = new abc();

console.log(objy.testProperty); //will display Hi, I am prototype property
objy.testProperty = Hi, I am over-ridden prototype property

console.log(objy.testProperty); //will display Hi, I am over-ridden prototype property

http://astutejs.blogspot.in/2015/10/javascript-prototype-is-easy.html

1
Jack Sane

Je ne suis pas sûr à 100%, mais je pense que la différence est que le deuxième exemple duplique simplement le contenu de la classe Car dans l'objet SuperCar, tandis que le premier lie le prototype SuperCar à la classe Car, de sorte que l'exécution change La classe voiture affecte également la classe SuperCar.

1
Jeff Ober