it-swarm.com.de

Warum muss der Prototyp-Konstruktor gesetzt werden?

Im Abschnitt über die Vererbung im MDN-Artikel Einführung in objektorientiertes Javascript habe ich festgestellt, dass sie den prototype.constructor gesetzt haben:

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;  

Dient dies einem wichtigen Zweck? Ist es in Ordnung, es wegzulassen?

269
trinth

Es ist nicht immer notwendig, aber es hat seine Verwendung. Angenommen, wir wollten eine Kopiermethode für die Basisvariable Person erstellen. So was:

// define the Person Class  
function Person(name) {
    this.name = name;
}  

Person.prototype.copy = function() {  
    // return new Person(this.name); // just as bad
    return new this.constructor(this.name);
};  

// define the Student class  
function Student(name) {  
    Person.call(this, name);
}  

// inherit Person  
Student.prototype = Object.create(Person.prototype);

Was passiert nun, wenn wir eine neue Student erstellen und kopieren? 

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => false

Die Kopie ist keine Instanz von Student. Dies liegt daran, dass wir (ohne explizite Überprüfungen) keine Möglichkeit haben, eine Student Kopie von der "Basis" -Klasse zurückzugeben. Wir können nur eine Person zurückgeben. Wenn wir den Konstruktor jedoch zurückgesetzt hätten:

// correct the constructor pointer because it points to Person  
Student.prototype.constructor = Student;

... dann funktioniert alles wie erwartet:

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => true
240
Wayne Burkett

Dient dies einem wichtigen Zweck?

Ja und nein.

In ES5 und früheren Versionen hat JavaScript selbst constructor für nichts verwendet. Es definiert, dass das Standardobjekt in der prototype-Eigenschaft einer Funktion es haben würde und dass es sich auf die Funktion beziehen würde, und das war es. Nichts anderes in der Spezifikation bezog sich darauf.

Das änderte sich in ES2015 (ES6), wo es in Bezug auf Vererbungshierarchien verwendet wurde. Beispielsweise verwendet Promise#then die constructor -Eigenschaft des Versprechens, auf das Sie es (über SpeciesConstructor ) aufrufen, wenn Sie das neue Versprechen erstellen, um zurückzukehren. Es ist auch in Subtyping Arrays involviert (via ArraySpeciesCreate ).

Außerhalb der Sprache selbst benutzten die Leute es manchmal, wenn sie generische "Klon" -Funktionen erstellen wollten oder einfach nur, wenn sie sich auf die Konstruktorfunktion des Objekts beziehen wollten. Meine Erfahrung ist, dass es selten ist, es zu benutzen, aber manchmal benutzen es Leute.

Ist es in Ordnung, es wegzulassen?

Es ist standardmäßig vorhanden. Sie müssen es nur zurücksetzen, wenn Sie das Objekt in der prototype-Eigenschaft einer Funktion ersetzen:

Student.prototype = Object.create(Person.prototype);

Wenn Sie das nicht tun:

Student.prototype.constructor = Student;

... dann erbt Student.prototype.constructor von Person.prototype, der (vermutlich) constructor = Person hat. Es ist also irreführend. Wenn Sie eine Unterklasse erstellen, die sie verwendet (wie Promise oder Array) und nicht class¹ verwenden (die dies für Sie erledigt), möchten Sie natürlich sicherstellen, dass Sie sie richtig einstellen. Also im Grunde: Es ist eine gute Idee.

Es ist in Ordnung, wenn nichts in Ihrem Code (oder in Ihrem Bibliothekscode) verwendet wird. Ich habe immer dafür gesorgt, dass es richtig verdrahtet war.

Mit dem class-Schlüsselwort von ES2015 (auch bekannt als ES6) hätten wir es natürlich meistens nicht mehr gebraucht, da es für uns erledigt wird, wenn wir es tun

class Student extends Person {
}

¹ "... wenn Sie etwas untergliedern, das es verwendet (wie Promise oder Array) und nicht class... verwenden" - Es ist möglich, aber das ist ein echter Schmerz (und ein bisschen dumm Sie müssen Reflect.construct verwenden.

66
T.J. Crowder

TLDR; Nicht super notwendig, aber wird wahrscheinlich auf lange Sicht helfen, und es ist genauer, dies zu tun.

HINWEIS: Viel bearbeitet, da meine vorherige Antwort verwirrend geschrieben war und einige Fehler aufwies, die ich in meinem Ansturm verpasst hatte, um zu antworten. Vielen Dank an diejenigen, die auf einige ungeheure Fehler hingewiesen haben.

Grundsätzlich geht es darum, die Unterklassen in Javascript korrekt zu verkabeln. Wenn wir eine Unterklasse bilden, müssen wir einige unkonventionelle Dinge tun, um sicherzustellen, dass die prototypische Delegierung ordnungsgemäß funktioniert, einschließlich des Überschreibens eines prototype -Objekts. Das Überschreiben eines prototype -Objekts enthält das constructor, daher müssen wir die Referenz korrigieren.

Lassen Sie uns schnell durchgehen, wie Klassen in ES5 funktionieren.

Angenommen, Sie haben eine Konstruktorfunktion und ihren Prototyp:

//Constructor Function
var Person = function(name, age) {
  this.name = name;
  this.age = age;
}

//Prototype Object - shared between all instances of Person
Person.prototype = {
  species: 'human',
}

Wenn Sie den Konstruktor zum Instanziieren aufrufen, sagen Sie Adam:

// instantiate using the 'new' keyword
var adam = new Person('Adam', 19);

Das mit 'Person' aufgerufene Schlüsselwort new führt den Person-Konstruktor im Grunde mit ein paar zusätzlichen Codezeilen aus:

function Person (name, age) {
  // This additional line is automatically added by the keyword 'new'
  // it sets up the relationship between the instance and the prototype object
  // So that the instance will delegate to the Prototype object
  this = Object.create(Person.prototype);

  this.name = name;
  this.age = age;

  return this;
}

/* So 'adam' will be an object that looks like this:
 * {
 *   name: 'Adam',
 *   age: 19
 * }
 */

Wenn wir console.log(adam.species), schlägt die Suche in der adam -Instanz fehl und sucht die prototypische Kette nach ihrem .prototype, der Person.prototype ist - und Person.prototype hat eine .species -Eigenschaft, sodass die Suche bei Person.prototype erfolgreich ist. Es wird dann 'human' protokollieren.

Hier zeigt der Person.prototype.constructor korrekt auf Person.

Also nun der interessante Teil, die sogenannte "Unterklasse". Wenn wir eine Student -Klasse erstellen möchten, die eine Unterklasse der Person -Klasse mit einigen zusätzlichen Änderungen ist, müssen wir sicherstellen, dass der Student.prototype.constructor aus Gründen der Genauigkeit auf Student zeigt.

Das macht es nicht von alleine. Wenn Sie eine Unterklasse bilden, sieht der Code folgendermaßen aus:

var Student = function(name, age, school) {
 // Calls the 'super' class, as every student is an instance of a Person
 Person.call(this, name, age);
 // This is what makes the Student instances different
 this.school = school
}

var eve = new Student('Eve', 20, 'UCSF');

console.log(Student.prototype); // this will be an empty object: {}

Wenn Sie hier new Student() aufrufen, wird ein Objekt mit allen gewünschten Eigenschaften zurückgegeben. Wenn wir hier eve instanceof Person überprüfen, wird false zurückgegeben. Wenn wir versuchen, auf eve.species zuzugreifen, wird undefined zurückgegeben.

Mit anderen Worten, wir müssen die Delegierung so verkabeln, dass eve instanceof Person true zurückgibt und dass Instanzen von Student korrekt an Student.prototype delegiert werden, und dann Person.prototype.

ABER da wir es mit dem new Schlüsselwort aufrufen, erinnern Sie sich, was dieser Aufruf hinzufügt? Es würde Object.create(Student.prototype) aufrufen. Auf diese Weise richten wir die Delegationsbeziehung zwischen Student und Student.prototype ein. Beachten Sie, dass Student.prototype derzeit leer ist. Wenn Sie also nach .species suchen, schlägt eine Instanz von Student fehl, da sie nur an Student.prototype delegiert und die Eigenschaft .species für Student.prototype nicht vorhanden ist. .

Wenn wir Object.create(Person.prototype)Student.prototype zuweisen, delegiert sich Student.prototype selbst an Person.prototype, und das Nachschlagen von eve.species gibt human zurück, wie wir es erwarten. Vermutlich möchten wir, dass es von Student.prototype AND Person.prototype erbt. Also müssen wir das alles reparieren.

/* This sets up the prototypal delegation correctly 
 *so that if a lookup fails on Student.prototype, it would delegate to Person's .prototype
 *This also allows us to add more things to Student.prototype 
 *that Person.prototype may not have
 *So now a failed lookup on an instance of Student 
 *will first look at Student.prototype, 
 *and failing that, go to Person.prototype (and failing /that/, where do we think it'll go?)
*/
Student.prototype = Object.create(Person.prototype);

Jetzt funktioniert die Delegierung, aber wir überschreiben Student.prototype mit Person.prototype. Wenn wir also Student.prototype.constructor aufrufen, zeigt dies auf Person anstelle von Student. Dies Deshalb müssen wir es beheben.

// Now we fix what the .constructor property is pointing to    
Student.prototype.constructor = Student

// If we check instanceof here
console.log(eve instanceof Person) // true

In ES5 ist unsere Eigenschaft constructor eine Referenz, die auf eine Funktion verweist, die wir mit der Absicht geschrieben haben, ein Konstruktor zu sein. Abgesehen von dem, was uns das Schlüsselwort new gibt, ist der Konstruktor ansonsten eine "einfache" Funktion.

In ES6 ist das constructor jetzt in die Art und Weise eingebaut, wie wir Klassen schreiben - wie in, es wird als Methode bereitgestellt, wenn wir eine Klasse deklarieren. Dies ist einfach syntaktischer Zucker, bietet uns jedoch einige Vorteile, wie den Zugriff auf ein super, wenn wir eine vorhandene Klasse erweitern. Also würden wir den obigen Code so schreiben:

class Person {
  // constructor function here
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  // static getter instead of a static property
  static get species() {
    return 'human';
  }
}

class Student extends Person {
   constructor(name, age, school) {
      // calling the superclass constructor
      super(name, age);
      this.school = school;
   }
}
12
bthehuman

Ich würde dem nicht zustimmen. Es ist nicht notwendig, den Prototyp festzulegen. Nehmen Sie den gleichen Code, entfernen Sie jedoch die Zeile prototype.constructor. Ändert sich etwas? Nehmen Sie jetzt die folgenden Änderungen vor:

Person = function () {
    this.favoriteColor = 'black';
}

Student = function () {
    Person.call(this);
    this.favoriteColor = 'blue';
}

und am Ende des Testcodes ...

alert(student1.favoriteColor);

Die Farbe wird blau sein.

Eine Änderung des prototype.constructor macht meiner Erfahrung nach wenig, es sei denn, Sie machen sehr spezifische, sehr komplizierte Dinge, die wahrscheinlich sowieso keine gute Praxis sind :)

Edit: Nachdem Sie das Web ein wenig durchforstet und ein wenig experimentiert haben, sieht es so aus, als würden die Leute den Konstruktor so einstellen, dass er aussieht wie das, was mit 'new' erstellt wird. Ich denke, ich würde behaupten, dass das Problem dabei ist, dass Javascript eine Prototypsprache ist - es gibt keine Erbschaft. Die meisten Programmierer haben jedoch einen Hintergrund der Programmierung, der das Erben als "den Weg" vorantreibt. Wir lassen uns also mit allen möglichen Dingen auseinandersetzen, um diese prototypische Sprache zu einer „klassischen“ Sprache zu machen, wie zum Beispiel das Erweitern von „Klassen“. In dem von ihnen angegebenen Beispiel ist ein neuer Schüler wirklich eine Person - er erstreckt sich nicht von einem anderen Schüler. Der Schüler beschäftigt sich ausschließlich mit der Person, und was auch immer die Person ist, der Schüler ist auch gut. Erweitern Sie den Schüler, und was Sie erweitert haben, ist ein Schüler im Herzen, der jedoch auf Ihre Bedürfnisse zugeschnitten ist. 

Crockford ist ein bisschen verrückt und übereifrig, aber lesen Sie einiges von dem, was er geschrieben hat ... und werden Sie dieses Zeug ganz anders betrachten.

10
Stephen

Das hat die große Falle, wenn Sie schreiben 

Student.prototype.constructor = Student;

aber dann, wenn es einen Lehrer gab, dessen Prototyp auch Person war und Sie geschrieben haben

Teacher.prototype.constructor = Teacher;

dann ist der Studentenbauer jetzt Lehrer!

Bearbeiten: Sie können dies vermeiden, indem Sie sicherstellen, dass Sie die Schüler- und Lehrerprototypen mithilfe neuer Instanzen der mit Object.create erstellten Person-Klasse festgelegt haben, wie im Mozilla-Beispiel.

Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
9
James D

Bis jetzt gibt es noch Verwirrung.

Dem ursprünglichen Beispiel folgend, da Sie ein vorhandenes Objekt student1 als haben:

var student1 = new Student("Janet", "Applied Physics");

Angenommen, Sie möchten nicht wissen, wie student1 erstellt wird. Sie möchten nur ein anderes Objekt wie dieses. Sie können die Konstruktoreigenschaft von student1 wie folgt verwenden:

var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");

Hier können die Eigenschaften nicht von Student abgerufen werden, wenn die Konstruktoreigenschaft nicht festgelegt ist. Es wird vielmehr ein Person-Objekt erstellt.

5
Mahavir

Ich habe ein schönes Codebeispiel dafür bekommen, warum es wirklich notwendig ist, den Prototyp-Konstruktor festzulegen.

function CarFactory(name){ 
   this.name=name;  
} 
CarFactory.prototype.CreateNewCar = function(){ 
    return new this.constructor("New Car "+ this.name); 
} 
CarFactory.prototype.toString=function(){ 
    return 'Car Factory ' + this.name;
} 

AudiFactory.prototype = new CarFactory();      // Here's where the inheritance occurs 
AudiFactory.prototype.constructor=AudiFactory;       // Otherwise instances of Audi would have a constructor of Car 

function AudiFactory(name){ 
    this.name=name;
} 

AudiFactory.prototype.toString=function(){ 
    return 'Audi Factory ' + this.name;
} 

var myAudiFactory = new AudiFactory('');
  alert('Hay your new ' + myAudiFactory + ' is ready.. Start Producing new audi cars !!! ');            

var newCar =  myAudiFactory.CreateNewCar(); // calls a method inherited from CarFactory 
alert(newCar); 

/*
Without resetting prototype constructor back to instance, new cars will not come from New Audi factory, Instead it will come from car factory ( base class )..   Dont we want our new car from Audi factory ???? 
*/
2
user3877965

Heutzutage ist es nicht mehr notwendig, Klassen mit Zuckerguss zu erstellen oder "Neu" zu verwenden. Verwenden Sie Objektliterale.

Der Object-Prototyp ist bereits eine "Klasse". Wenn Sie ein Objektliteral definieren, ist dies bereits eine Instanz des Prototypobjekts. Diese können auch als Prototyp eines anderen Objekts dienen usw.

const Person = {
  name: '[Person.name]',
  greeting: function() {
    console.log( `My name is ${ this.name || '[Name not assigned]' }` );
  }
};
// Person.greeting = function() {...} // or define outside the obj if you must

// Object.create version
const john = Object.create( Person );
john.name = 'John';
console.log( john.name ); // John
john.greeting(); // My name is John 
// Define new greeting method
john.greeting = function() {
    console.log( `Hi, my name is ${ this.name }` )
};
john.greeting(); // Hi, my name is John

// Object.assign version
const jane = Object.assign( Person, { name: 'Jane' } );
console.log( jane.name ); // Jane
// Original greeting
jane.greeting(); // My name is Jane 

// Original Person obj is unaffected
console.log( Person.name ); // [Person.name]
console.log( Person.greeting() ); // My name is [Person.name]

Das ist eine Lektüre :

Klassenbasierte objektorientierte Sprachen wie Java und C++ sind gegründet auf dem Konzept von zwei verschiedenen Entitäten: Klassen und Instanzen.

...

Eine prototypbasierte Sprache wie JavaScript macht dies nicht Unterscheidung: Es hat einfach Objekte. Eine prototypbasierte Sprache hat die Vorstellung eines prototypischen Objekts, eines als Vorlage verwendeten Objekts aus um die anfänglichen Eigenschaften für ein neues Objekt zu erhalten. Jedes Objekt kann Legen Sie seine eigenen Eigenschaften fest, entweder beim Erstellen oder zur Laufzeit . Darüber hinaus kann jedes Objekt als Prototyp für ein anderes .__ zugeordnet werden. object, damit das zweite Objekt das .__ des ersten Objekts gemeinsam nutzen kann. Eigenschaften

1
ucsarge

Dies ist erforderlich, wenn Sie eine Alternative zu toString ohne Monkeypatching benötigen:

//Local
foo = [];
foo.toUpperCase = String(foo).toUpperCase;
foo.Push("a");
foo.toUpperCase();

//Global
foo = [];
window.toUpperCase = function (obj) {return String(obj).toUpperCase();}
foo.Push("a");
toUpperCase(foo);

//Prototype
foo = [];
Array.prototype.toUpperCase = String.prototype.toUpperCase;
foo.Push("a");
foo.toUpperCase();

//toString alternative via Prototype constructor
foo = [];
Array.prototype.constructor = String.prototype.toUpperCase;
foo.Push("a,b");
foo.constructor();

//toString override
var foo = [];
foo.Push("a");
var bar = String(foo);
foo.toString = function() { return bar.toUpperCase(); }
foo.toString();

//Object prototype as a function
Math.prototype = function(char){return Math.prototype[char]};
Math.prototype.constructor = function() 
  {
  var i = 0, unicode = {}, zero_padding = "0000", max = 9999;
  
  while (i < max) 
    {
    Math.prototype[String.fromCharCode(parseInt(i, 16))] = ("u" + zero_padding + i).substr(-4);

    i = i + 1;
    }    
  }

Math.prototype.constructor();
console.log(Math.prototype("a") );
console.log(Math.prototype["a"] );
console.log(Math.prototype("a") === Math.prototype["a"]);

1
Paul Sweatte

EDIT, ich habe mich eigentlich geirrt. Das Auskommentieren der Zeile ändert nichts an ihrem Verhalten. (Ich habe es getestet)


Ja, das ist notwendig. Wenn Sie das tun 

Student.prototype = new Person();  

Student.prototype.constructor wird zu Person. Der Aufruf von Student() würde daher ein von Person erstelltes Objekt zurückgeben. Wenn du es dann tust

Student.prototype.constructor = Student; 

Student.prototype.constructor wird auf Student zurückgesetzt. Wenn Sie nun Student() aufrufen, führt es Student aus, wodurch der übergeordnete Konstruktor Parent() aufgerufen wird. Wenn Sie Student.prototype.constructor vor dem Aufruf nicht zurückgesetzt haben, erhalten Sie ein Objekt, für das keine der in Student() festgelegten Eigenschaften festgelegt ist.

0
invisible bob

Gegebene einfache Konstruktorfunktion:

function Person(){
    this.name = 'test';
}


console.log(Person.prototype.constructor) // function Person(){...}

Person.prototype = { //constructor in this case is Object
    sayName: function(){
        return this.name;
    }
}

var person = new Person();
console.log(person instanceof Person); //true
console.log(person.sayName()); //test
console.log(Person.prototype.constructor) // function Object(){...}

Standardmäßig (aus der Spezifikation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor ) erhalten alle Prototypen automatisch eine als Konstruktor bezeichnete Eigenschaft, die zurück verweist auf die Funktion, auf der es eine Eigenschaft ist. .__ Abhängig vom Konstruktor können dem Prototyp andere Eigenschaften und Methoden hinzugefügt werden. Dies ist zwar keine gängige Praxis, ist aber für Erweiterungen zulässig. 

Also einfach beantworten: Wir müssen sicherstellen, dass der Wert in prototype.constructor richtig eingestellt ist, da er von der Spezifikation angenommen wird.

Müssen wir diesen Wert immer richtig einstellen? Es hilft beim Debuggen und macht die interne Struktur gegenüber der Spezifikation konsistent. Wir sollten es definitiv tun, wenn unsere API von Drittanbietern verwendet wird, aber nicht wirklich, wenn der Code in der Laufzeit endgültig ausgeführt wird.

0
kospiotr

Hier ist ein Beispiel von MDN, das ich sehr hilfreich fand, um seine Verwendung zu verstehen.

In JavaScript haben wir async functions , das AsyncFunction object zurückgibt. AsyncFunction ist kein globales Objekt, es kann jedoch mit der constructor-Eigenschaft abgerufen und verwendet werden.

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

// AsyncFunction constructor
var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor

var a = new AsyncFunction('a', 
                          'b', 
                          'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);');

a(10, 20).then(v => {
  console.log(v); // prints 30 after 4 seconds
});
0
Hitesh Kumar