web-dev-qa-db-de.com

Verstehen von prototypischer Vererbung in JavaScript

Ich bin neu bei JavaScript OOP. Können Sie bitte den Unterschied zwischen den folgenden Codeblöcken erklären? Ich habe getestet und beide Blöcke funktionieren. Was ist die beste Praxis und warum?

Erster Block:

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();

Zweiter Block:

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();

Warum hat der Autor die Drive- und Fly-Methoden mit prototype hinzugefügt und sie nicht als this.Drive-Methode in der Car-Klasse und als this.Fly in der SuperCar-Klasse deklariert?

Warum muss SuperCar.prototype.constructor auf SuperCar zurückgesetzt werden? Wird die constructor-Eigenschaft überschrieben, wenn prototype eingestellt ist? Ich habe diese Zeile auskommentiert und nichts geändert.

Warum rufen Sie Car.call(this, name); im Konstruktor SuperCar auf? Werden Eigenschaften und Methoden von Car nicht "vererbt", wenn ich dies tue

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

Die beiden Blöcke unterscheiden sich dahingehend, dass im ersten Beispiel Drive() nur einmal vorhanden ist, während bei der zweiten Annäherung Drive() pro Instanz vorhanden ist (jedes Mal, wenn Sie new Car() die Funktion drive() wird erneut erstellt). Oder anders gesagt, der erste verwendet den Prototyp zum Speichern der Funktion und der zweite den Konstruktor. Die Suche nach Funktionen ist Konstruktor und dann Prototyp. Für Ihre Suche nach Drive() findet sie es unabhängig davon, ob es sich im Konstruktor oder im Prototyp befindet. Die Verwendung des Prototyps ist effizienter, da Sie normalerweise nur einmal pro Typ eine Funktion benötigen.

Der Aufruf new in Javascript setzt den Konstruktor im Prototyp automatisch. Wenn Sie den Prototyp überschreiben, müssen Sie den Konstruktor manuell einstellen.

Vererbung in Javascript hat nichts wie super. Wenn Sie also eine Unterklasse haben, besteht die einzige Möglichkeit, den Superkonstruktor aufzurufen, beim Namen.

81
Norbert Hartl

Zum Hinzufügen von der Antwort von Norbert Hartl ist SuperCar.prototype.constructor nicht erforderlich, aber einige verwenden es als bequeme Möglichkeit, die Konstruktionsfunktion eines Objekts (in diesem Fall SuperCar-Objekte) zu erhalten.

Nur aus dem ersten Beispiel ist Car.call (dieser Name) in der SuperCar-Konstruktorfunktion, denn wenn Sie Folgendes tun:

var mySuperCar = new SuperCar("SuperCar");

Dies ist, was JavaScript tut:

  1. Ein neues, leeres Objekt wird instanziiert.
  2. Der interne Prototyp des frischen Objekts ist auf Auto eingestellt.
  3. Die SuperCar-Konstruktorfunktion wird ausgeführt.
  4. Das fertige Objekt wird zurückgegeben und in mySuperCar gesetzt.

Beachten Sie, dass JavaScript für Sie kein Auto genannt hat. Prototypen wie sie sind, jede Eigenschaft oder Methode, die Sie nicht selbst für SuperCar festlegen, wird in Car nachgeschlagen. Manchmal ist das gut, z. SuperCar verfügt nicht über eine Drive-Methode, kann jedoch die Car-Methode gemeinsam nutzen, sodass alle SuperCars die gleiche Drive-Methode verwenden. Andere Zeiten, in denen Sie nicht teilen möchten, wie jeder SuperCar seinen eigenen Namen hat. Wie kann man also den Namen jedes SuperCars auf sein eigenes Ding festlegen? Sie können this.Name in der SuperCar-Konstruktorfunktion festlegen:

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

Das funktioniert, aber warten Sie eine Sekunde. Haben wir im Autobauer nicht genau dasselbe gemacht? Ich will uns nicht wiederholen. Da Car den Namen bereits festlegt, nennen wir es einfach.

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

Hoppla, Sie wollen niemals die spezielle this-Objektreferenz ändern. Erinnern Sie sich an die 4 Schritte? Hängen Sie an dem Objekt, das Sie von JavaScript erhalten haben, denn nur so können Sie die kostbare interne Prototypverbindung zwischen Ihrem SuperCar-Objekt und Car beibehalten. Wie setzen wir also Namen, ohne uns zu wiederholen und unser neues SuperCar-Objekt nicht wegzuwerfen, weil JavaScript so viel Aufwand für die Vorbereitung auf uns aufgewendet hat?

Zwei Dinge. Erstens: Die Bedeutung von this ist flexibel. Zweitens: Auto ist eine Funktion. Es ist möglich, Car nicht mit einem makellosen, frisch instanziierten Objekt zu rufen, sondern mit einem SuperCar-Objekt. Das gibt uns die endgültige Lösung, die Teil des ersten Beispiels in Ihrer Frage ist:

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

Als Funktion kann Car mit der call-Methode der Funktion aufgerufen werden, wodurch die Bedeutung von this in Car in die SuperCar-Instanz geändert wird, die wir gerade aufbauen. Presto! Jetzt erhält jeder SuperCar eine eigene Name-Eigenschaft.

Zusammenfassend gibt Car.call(this, name) im SuperCar-Konstruktor jedem neuen SuperCar-Objekt seine eigene eindeutige Name-Eigenschaft, jedoch ohne den Code zu duplizieren, der bereits in Car vorhanden ist.

Prototypen sind nicht unheimlich, wenn Sie sie verstanden haben, aber sie ähneln dem klassischen Klassen-/Vererbungsmodell OOP überhaupt nicht. Ich habe einen Artikel über das Prototypenkonzept in JavaScript geschrieben. Es wurde für eine Spiel-Engine geschrieben, die JavaScript verwendet, aber es ist dieselbe JavaScript-Engine, die auch von Firefox verwendet wird. Daher sollte alles relevant sein. Hoffe das hilft.

139
Tung Nguyen

Norbert, Sie sollten beachten, dass Ihr erstes Beispiel ziemlich genau das ist, was Douglas Crockford als pseudoklassische Vererbung bezeichnet. Etwas zu beachten:

  1. Sie rufen den Car-Konstruktor zweimal auf, einmal aus der Zeile SuperCar.prototype = new Car () und die andere aus der Zeile "Constructor Stealing" Car.call (dies ... Sie können eine Hilfsmethode erstellen, um stattdessen Prototypen zu erben Der Autohersteller muss nur einmal laufen, wodurch das Setup effizienter wird.
  2. Mit der Zeile SuperCar.prototype.constructor = SuperCar können Sie den Konstruktor anhand von instanceof identifizieren. Einige Leute wollen dies, andere vermeiden die Verwendung von instanceof
  3. Referenz-Vars wie: var arr = ['one', 'two'], wenn sie auf dem Super definiert sind (zB Car), werden von ALLEN Instanzen gemeinsam genutzt. Dies bedeutet, dass inst1.arr.Push ['three'], inst2.arr.Push ['four'] usw. für alle Instanzen angezeigt wird! Im Wesentlichen statisches Verhalten, das Sie wahrscheinlich nicht möchten.
  4. Ihr zweiter Block definiert die fly-Methode im Konstruktor. Dies bedeutet, dass bei jedem Aufruf ein "Methodenobjekt" erstellt wird. Besser einen Prototyp für Methoden verwenden! Sie KÖNNEN es jedoch im Konstruktor behalten, wenn Sie möchten - Sie müssen nur schützen, damit Sie das Prototyp-Literal nur einmal initialisieren (Pseudo): if (SuperCar.prototype.myMethod! = 'Function') ... und dann definieren Ihr Prototyp-Literal.
  5. "Warum Car.call (diesen Namen) anrufen ...": Ich habe keine Zeit, Ihren Code genau zu betrachten. Ich kann mich also irren, aber dies ist normalerweise so, dass jede Instanz ihren eigenen Zustand beibehalten kann, um das Problem zu beheben 'staticy' Verhaltensproblem der Prototyp-Verkettung, das ich oben beschrieben habe.

Zum Schluss möchte ich noch erwähnen, dass ich mehrere Beispiele für TDD-JavaScript-Vererbungscode habe, der hier funktioniert: TDD-JavaScript-Vererbungscode und Essay Ich würde gerne Ihr Feedback erhalten, da ich es verbessern und behalten möchte es ist Open Source. Das Ziel ist es, klassischen Programmierern zu helfen, sich schnell mit JavaScript vertraut zu machen und die beiden Bücher Crockford und Zakas zu ergänzen.

8
Rob
function abc() {
}

Prototypmethoden und Eigenschaften, die für die Funktion abc erstellt wurden

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

Neue Instanzen für die Funktion abc anlegen

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

Ich bin nicht zu 100% sicher, aber ich glaube, der Unterschied besteht darin, dass das zweite Beispiel einfach den Inhalt der Car-Klasse in das SuperCar-Objekt kopiert, während das erste den SuperCar-Prototyp mit der Car-Klasse verknüpft, sodass sich die Laufzeit in der ändert Die Fahrzeugklasse betrifft auch die SuperCar-Klasse.

1
Jeff Ober

Hier gibt es mehrere Fragen:

Können Sie bitte den Unterschied zwischen den folgenden Codeblöcken erklären. Ich habe getestet und beide Blöcke funktionieren.

Die erste erstellt nur eine Drive -Funktion, die zweite erstellt zwei davon: eine für myCar und eine für mySuperCar.

Hier ist Code, der beim Ausführen des ersten oder zweiten Blocks unterschiedliche Ergebnisse liefert:

myCar.Fly === mySuperCar.Fly // true only in the first case
Object.keys(myCar).includes("Fly") // true only in the second case
Object.keys(Car.prototype).length === 0 // true only in the second case

Was ist die beste Vorgehensweise und warum?
Warum hat der Autor Drive und Fly Methoden mit prototype hinzugefügt, aber nicht als this.Drive Methode deklariert in Car Klasse und this.Fly in SuperCar Klasse?

Es ist besser, Methoden am Prototyp zu definieren, weil:

  • jede Methode ist nur einmal definiert
  • jede Methode ist auch für Instanzen verfügbar, die ohne Ausführung des Konstruktors erstellt wurden (dies ist der Fall, wenn Object.create(Car.prototype) aufgerufen wird).
  • sie können überprüfen, auf welcher Ebene in der Prototypenkette einer Instanz eine bestimmte Methode definiert wurde.

Warum muss SuperCar.prototype.constructor auf SuperCar zurückgesetzt werden? Wird die Eigenschaft constructor überschrieben, wenn prototype festgelegt wird? Ich habe diese Zeile auskommentiert und nichts geändert.

Die Eigenschaft constructor wird nicht überschrieben, wenn prototype festgelegt ist. Aber der Konstruktor von new Car() ist Car. Wenn Sie also new Car() auf SuperCar.prototype setzen, ist SuperCar.prototype.constructor offensichtlich Car.

Solange Sie prototype nicht neu zuweisen, gibt es eine Invarianz: Constructor.prototype.constructor === Constructor. Dies gilt beispielsweise für Car: Car.prototype.constructor === Car, aber ebenso für Array, Object, String ]_, ...usw.

Wenn Sie jedoch prototype ein anderes Objekt zuweisen, wird diese Invarianz aufgehoben. Normalerweise ist dies kein Problem (wie Sie bemerkt haben), aber es ist besser, es wieder herzustellen, da es die Frage beantwortet "Welcher Konstruktor verwendet dieses Prototypobjekt beim Erstellen neuer Instanzen?" Etwas Code kann eine solche Inspektion tun und davon abhängen. Siehe "Warum muss der Prototyp-Konstruktor festgelegt werden?" für solche Fälle.

Warum im Konstruktor SuperCarCar.call(this, name); aufrufen? Werden Eigenschaften und Methoden von Car nicht "geerbt", wenn ich das tue?

var myCar = new Car("Car");

Wenn Sie Car.call(this, name); nicht ausführen, hat Ihre SuperCar -Instanz keine Namenseigenschaft. Sie können sich natürlich dafür entscheiden, stattdessen nur this.name = name; auszuführen, wodurch nur der Code kopiert wird, der sich im Konstruktor Car befindet. In komplexeren Situationen wäre es jedoch eine schlechte Praxis, eine solche Codeduplizierung durchzuführen .

Es wäre nicht hilfreich, new Car(name) im Konstruktor SuperCar aufzurufen, da dadurch ein anderes Objekt erstellt wird, während Sie das this Objekt. Wenn Sie new nicht verwenden (stattdessen call verwenden), weisen Sie die Funktion Car tatsächlich an, nicht als Konstruktor auszuführen (dh not ein neues Objekt erstellen), aber um das Objekt zu verwenden, übergeben Sie es stattdessen.

Die Zeiten haben sich geändert

In modernen Versionen von JavaScript können Sie super(name) anstelle von Car.call(this, name) verwenden:

function SuperCar(name) {
    super(name);
}

Heute würden Sie auch die Syntax class verwenden und den ersten Codeblock der Frage wie folgt schreiben:

class Car {
    constructor(name) {
        this.name = name;
    }
    drive() {
        console.log(`My name is ${this.name} and I'm driving.`);
    }
}

class SuperCar extends Car {
    constructor(name) {
        super(name);
    }
    fly() {
        console.log(`My name is ${this.name} and I'm flying!`);
    }
}

const myCar = new Car("Car");
myCar.drive();

const mySuperCar = new SuperCar("SuperCar");
mySuperCar.drive();
mySuperCar.fly();

Beachten Sie, dass Sie die Eigenschaft prototype nicht einmal erwähnen müssen, um das Ziel zu erreichen. Die class ... extends -Syntax sorgt auch dafür, dass die prototype.constructor -Eigenschaft als erster Block in Ihrer Frage festgelegt wird.

0
trincot