Javascript-Vererbung: Superkonstruktor aufrufen oder Prototypkette verwenden?

81

Vor kurzem habe ich über die Verwendung von JavaScript-Aufrufen in MDC gelesen

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/call

Eine Linke des unten gezeigten Beispiels verstehe ich immer noch nicht.

Warum verwenden sie hier so Vererbung?

Prod_dept.prototype = new Product();

ist das notwendig? Weil es einen Aufruf an den Superkonstruktor in gibt

Prod_dept()

sowieso so

Product.call

ist das nur aus allgemeinem Verhalten? Wann ist es besser, call for the super-constructor oder die Prototypenkette zu verwenden?

function Product(name, value){
  this.name = name;
  if(value >= 1000)
    this.value = 999;
  else
    this.value = value;
}

function Prod_dept(name, value, dept){
  this.dept = dept;
  Product.call(this, name, value);
}

Prod_dept.prototype = new Product();

// since 5 is less than 1000, value is set
cheese = new Prod_dept("feta", 5, "food");

// since 5000 is above 1000, value will be 999
car = new Prod_dept("honda", 5000, "auto");

Vielen Dank, dass Sie die Dinge klarer gemacht haben

Jeremy S.
quelle
Die Art und Weise, wie Sie es verwendet haben, ist fast richtig, aber Sie möchten möglicherweise Object.create () verwenden, anstatt die Basis mit dem neuen Schlüsselwort zu instanziieren (kann Probleme verursachen, wenn der Basiskonstruktor Argumente benötigt). Ich habe Details in meinem Blog: ncombo.wordpress.com/2013/07/11/…
Jon
2
Beachten Sie auch, dass Product () effektiv zweimal aufgerufen wird.
event_jr

Antworten:

109

Die Antwort auf die eigentliche Frage lautet, dass Sie beides tun müssen:

  • Wenn Sie den Prototyp auf eine Instanz des übergeordneten Elements setzen, wird die Prototypkette (Vererbung) initialisiert. Dies erfolgt nur einmal (da das Prototypobjekt gemeinsam genutzt wird).
  • Wenn Sie den Konstruktor des übergeordneten Elements aufrufen, wird das Objekt selbst initialisiert. Dies erfolgt bei jeder Instanziierung (Sie können jedes Mal, wenn Sie es erstellen, unterschiedliche Parameter übergeben).

Daher sollten Sie beim Einrichten der Vererbung nicht den Konstruktor des übergeordneten Elements aufrufen. Nur beim Instanziieren eines Objekts, das von einem anderen erbt.

Chris Morgans Antwort ist fast vollständig und es fehlt ein kleines Detail (Konstruktoreigenschaft). Lassen Sie mich eine Methode zum Einrichten der Vererbung vorschlagen.

function extend(base, sub) {
  // Avoid instantiating the base class just to setup inheritance
  // Also, do a recursive merge of two prototypes, so we don't overwrite 
  // the existing prototype, but still maintain the inheritance chain
  // Thanks to @ccnokes
  var origProto = sub.prototype;
  sub.prototype = Object.create(base.prototype);
  for (var key in origProto)  {
     sub.prototype[key] = origProto[key];
  }
  // The constructor property was set wrong, let's fix it
  Object.defineProperty(sub.prototype, 'constructor', { 
    enumerable: false, 
    value: sub 
  });
}

// Let's try this
function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  sayMyName: function() {
    console.log(this.getWordsToSay() + " " + this.name);
  },
  getWordsToSay: function() {
    // Abstract
  }
}

function Dog(name) {
  // Call the parent's constructor
  Animal.call(this, name);
}

Dog.prototype = {
    getWordsToSay: function(){
      return "Ruff Ruff";
    }
}    

// Setup the prototype chain the right way
extend(Animal, Dog);

// Here is where the Dog (and Animal) constructors are called
var dog = new Dog("Lassie");
dog.sayMyName(); // Outputs Ruff Ruff Lassie
console.log(dog instanceof Animal); // true
console.log(dog.constructor); // Dog

In meinem Blogbeitrag finden Sie noch mehr syntaktischen Zucker beim Erstellen von Klassen. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

Technik kopiert von Ext-JS und http://www.uselesspickles.com/class_library/ und ein Kommentar von https://stackoverflow.com/users/1397311/ccnokes

Juan Mendes
quelle
6
In EcmaScript5 + (alle modernen Browser) können Sie es nicht aufzählbar machen, wenn Sie es so definieren. Auf diese Object.defineProperty(sub.protoype, 'constructor', { enumerable: false, value: sub }); Weise erhalten Sie genau das gleiche "Verhalten" wie beim Erstellen einer neuen Instanz einer Funktion durch Javascript (der Konstruktor wird als aufzählbar festgelegt) = automatisch falsch)
Adaptabi
2
Könnten Sie die Erweiterungsmethode nicht einfach auf zwei Zeilen vereinfachen? Nämlich: sub.prototype = Object.create (base.prototype); sub.prototype.constructor = sub;
Appetere
@Steve Ja, Sie können, als ich das zum ersten Mal schrieb, Object.createnicht so gut unterstützt werden ... es zu aktualisieren. Beachten Sie, dass die meisten Object.createPolyfills mit der ursprünglich gezeigten Technik implementiert werden.
Juan Mendes
1
Wenn ich also nur dem untergeordneten Objekt und seinen Instanzen, in diesem Fall dem "Dog" -Objekt, Methoden hinzufügen möchte, würden Sie einfach die beiden Prototypen in Ihrer Erweiterungsfunktion wie folgt zusammenführen : jsfiddle.net/ccnokes/75f9P ?
ccnokes
1
@ elad.chen Der Ansatz, den ich in der Antwort beschrieben habe, verkettet den Prototyp. Mixins kopieren normalerweise alle Eigenschaften in eine Instanz, nicht in den Prototyp. Siehe stackoverflow.com/questions/7506210/…
Juan Mendes
30

Der ideale Weg, dies zu tun, ist, es nicht zu tun Prod_dept.prototype = new Product();, da dies den ProductKonstruktor aufruft . Der ideale Weg ist also, es zu klonen, mit Ausnahme des Konstruktors, ungefähr so:

function Product(...) {
    ...
}
var tmp = function(){};
tmp.prototype = Product.prototype;

function Prod_dept(...) {
    Product.call(this, ...);
}
Prod_dept.prototype = new tmp();
Prod_dept.prototype.constructor = Prod_dept;

Dann wird der Superkonstruktor zur Konstruktionszeit aufgerufen, was Sie wollen, denn dann können Sie auch die Parameter übergeben.

Wenn Sie sich Dinge wie die Google Closure Library ansehen, werden Sie sehen, dass sie so vorgehen.

Chris Morgan
quelle
Ich nenne diesen Konstruktor, der zum Einrichten der Vererbung verwendet wird, den Ersatzkonstruktor. Ihr Beispiel vergisst immer noch, die Konstruktoreigenschaft nach dem Einrichten der Vererbung zurückzusetzen, damit Sie den Konstruktor richtig erkennen können
Juan Mendes
1
@ Juan: OK, aktualisiert, um hinzuzufügen Prod_dept.prototype.constructor = Prod_dept;.
Chris Morgan
@ ChrisMorgan Ich habe Probleme, die letzte Zeile in Ihrem Beispiel zu verstehen : Prod_dept.prototype.constructor = Prod_dept;. Zuallererst, warum wird es benötigt und warum zeigt es auf Prod_deptstatt Product?
Lasse Christiansen
1
@ LasseChristiansen-sw_lasse: Prod_dept.prototypewird als Prototyp der Ausgabe von verwendet new Prod_dept(). (In der Regel ist dieser Prototyp als verfügbar instance.__proto__, obwohl dies ein Implementierungsdetail ist.) Warum constructor- er ist ein Standardteil der Sprache und sollte daher aus Gründen der Konsistenz bereitgestellt werden. Standardmäßig ist es richtig, aber da wir den Prototyp vollständig ersetzen, müssen wir den richtigen Wert erneut zuweisen, da sonst einige Dinge nicht vernünftig sind (in diesem Fall würde dies bedeuten, dass eine Prod_deptInstanz this.constructor == Productschlecht wäre).
Chris Morgan
6

Wenn Sie die objektorientierte Programmierung in JavaScript durchgeführt haben, wissen Sie, dass Sie eine Klasse wie folgt erstellen können:

Person = function(id, name, age){
    this.id = id;
    this.name = name;
    this.age = age;
    alert('A new person has been accepted');
}

Bisher hat unsere Klassenperson nur zwei Eigenschaften und wir werden ihr einige Methoden geben. Ein sauberer Weg, dies zu tun, ist die Verwendung des "Prototyp" -Objekts. Ab JavaScript 1.1 wurde das Prototypobjekt in JavaScript eingeführt. Dies ist ein integriertes Objekt, das das Hinzufügen benutzerdefinierter Eigenschaften und Methoden zu allen Instanzen eines Objekts vereinfacht. Fügen wir unserer Klasse zwei Methoden hinzu, indem wir das 'Prototyp'-Objekt wie folgt verwenden:

Person.prototype = {
    /** wake person up */
    wake_up: function() {
        alert('I am awake');
    },

    /** retrieve person's age */
    get_age: function() {
        return this.age;
    }
}

Jetzt haben wir unsere Klasse Person definiert. Was wäre, wenn wir eine andere Klasse namens Manager definieren wollten, die einige Eigenschaften von Person erbt? Es macht keinen Sinn, all diese Eigenschaften erneut zu definieren, wenn wir unsere Manager-Klasse definieren. Wir können sie einfach so einstellen, dass sie von der Klasse Person erbt. JavaScript hat keine eingebaute Vererbung, aber wir können eine Technik verwenden, um die Vererbung wie folgt zu implementieren:

Inheritance_Manager = {};// Wir erstellen eine Vererbungsmanagerklasse (der Name ist beliebig)

Geben wir unserer Vererbungsklasse nun eine Methode namens extens, die die Argumente baseClass und subClassas verwendet. Innerhalb der Extend-Methode erstellen wir eine innere Klasse namens Vererbungsfunktion inheritance () {}. Der Grund, warum wir diese innere Klasse verwenden, besteht darin, Verwechslungen zwischen den Prototypen baseClass und subClass zu vermeiden. Als nächstes verweisen wir den Prototyp unserer Vererbungsklasse wie folgt auf den baseClass-Prototyp: inheritance.prototype = baseClass. Prototyp; Dann kopieren wir den Vererbungsprototyp wie folgt in den Unterklassenprototyp: subClass.prototype = new inheritance (); Als nächstes müssen Sie den Konstruktor für unsere Unterklasse wie folgt angeben: subClass.prototype.constructor = subClass; Sobald wir mit unserem SubClass-Prototyping fertig sind, können wir die nächsten beiden Codezeilen angeben, um einige Basisklassenzeiger festzulegen.

subClass.baseConstructor = baseClass;
subClass.superClass = baseClass.prototype;

Hier ist der vollständige Code für unsere Erweiterungsfunktion:

Inheritance_Manager.extend = function(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype = baseClass.prototype;
    subClass.prototype = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.baseConstructor = baseClass;
    subClass.superClass = baseClass.prototype;
}

Nachdem wir unsere Vererbung implementiert haben, können wir damit beginnen, unsere Klassen zu erweitern. In diesem Fall erweitern wir unsere Person-Klasse wie folgt in eine Manager-Klasse:

Wir definieren die Manager-Klasse

Manager = function(id, name, age, salary) {
    Person.baseConstructor.call(this, id, name, age);
    this.salary = salary;
    alert('A manager has been registered.');
}

Wir lassen es von Person erben

Inheritance_Manager.extend(Manager, Person);

Wenn Sie bemerkt haben, haben wir gerade die Extend-Methode unserer Inheritance_Manager-Klasse aufgerufen und in unserem Fall den subClass Manager und dann die baseClass Person übergeben. Beachten Sie, dass die Reihenfolge hier sehr wichtig ist. Wenn Sie sie austauschen, funktioniert die Vererbung nicht wie beabsichtigt, wenn überhaupt. Beachten Sie auch, dass Sie diese Vererbung angeben müssen, bevor Sie unsere Unterklasse tatsächlich definieren können. Definieren wir nun unsere Unterklasse:

Wir können weitere Methoden wie die folgende hinzufügen. In unserer Manager-Klasse sind immer die Methoden und Eigenschaften in der Person-Klasse definiert, da sie von dieser erbt.

Manager.prototype.lead = function(){
   alert('I am a good leader');
}

Um es zu testen, erstellen wir zwei Objekte, eines aus der Klasse Person und eines aus dem geerbten Klassenmanager:

var p = new Person(1, 'Joe Tester', 26);
var pm = new Manager(1, 'Joe Tester', 26, '20.000');

Den vollständigen Code und weitere Kommentare erhalten Sie unter: http://www.cyberminds.co.uk/blog/articles/how-to-implement-javascript-inheritance.aspx

Joe Francis
quelle