Dlaczego konieczne jest ustawienie konstruktora prototypu?

W sekcji o dziedziczeniu w artykule MDN Wprowadzenie do Obiektowego Javascript, zauważyłem, że ustawili prototyp.konstruktor:

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;  
Czy służy to jakiemuś ważnemu celowi? Można to pominąć?
Author: Joshua Taylor, 2011-12-10

12 answers

Nie zawsze jest to konieczne, ale ma swoje zastosowania. Załóżmy, że chcemy utworzyć metodę kopiowania na klasie base Person. Tak:

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

Co się stanie, gdy stworzymy nowy Student i skopiujemy go?

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

Kopia nie jest instancją Student. Dzieje się tak, ponieważ (bez jawnych sprawdzeń), nie mielibyśmy możliwości zwrócenia Student kopii z klasy "base". Możemy zwrócić tylko Person. Jednak, gdybyśmy zresetowali konstruktor:

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

...wtedy wszystko działa zgodnie z oczekiwaniami:

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => true
 233
Author: Wayne Burkett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-01 14:05:29
Czy to służy jakiemuś ważnemu celowi?
Tak i nie.

W ES5 i wcześniejszych JavaScript nie używał constructor do niczego. Zdefiniowano, że domyślny obiekt we właściwości prototype funkcji będzie miał tę właściwość i że odwoła się ona z powrotem do funkcji, a to było to. Nic innego w specyfikacji nie odnosiło się do niego w ogóle.

To zmieniło się w ES2015( ES6), który zaczął używać go w odniesieniu do hierarchii dziedziczenia. Na instancja, Promise#then używa właściwości constructor obietnicy, którą wywołujesz (poprzez SpeciesConstructor ) podczas budowania nowej obietnicy powrotu. Jest również zaangażowany w podtypowanie tablic (poprzez ArraySpeciesCreate).

Poza samym językiem, czasami ludzie używali go, gdy próbowali zbudować ogólne funkcje "klonowania" lub po prostu ogólnie, gdy chcieli odnieść się do tego, co uważali za funkcję konstruktora obiektu. Moje doświadczenie polega na tym, że używanie jest rzadki, ale czasami ludzie go używają.

Czy można to pominąć?

Jest tam domyślnie, musisz go tylko umieścić z powrotem, gdy podmienisz obiekt na właściwości prototype funkcji:

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

Jeśli tego nie zrobisz:

Student.prototype.constructor = Student;

...następnie Student.prototype.constructor dziedziczy z Person.prototype, który (przypuszczalnie) ma constructor = Person. Więc to wprowadza w błąd. I oczywiście, jeśli podklasujesz coś, co go używa (jak Promise lub Array) i nie używasz class1 (które obsługuje to dla ciebie), będziesz chciał się upewnić, że ustawiłeś go poprawnie. Więc zasadniczo: to dobry pomysł.

Jest w porządku, jeśli nic w Twoim kodzie (lub kodzie bibliotecznym, którego używasz) go nie używa. Zawsze upewniałem się, że jest prawidłowo podłączony.

Oczywiście, ze słowem kluczowym ES2015 (aka ES6) class, przez większość czasu użylibyśmy go, nie musimy już tego robić, ponieważ jest obsługiwany za nas, gdy to robimy]}
class Student extends Person {
}

¹ "...jeśli podklasujesz coś, co go używa (jak Promise lub Array) i nie używać class..." - to jest możliwe, aby to zrobić, ale to prawdziwy ból (i trochę głupie). Musisz użyć Reflect.construct.

 63
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-22 12:37:06

Nie zgadzam się. Nie jest konieczne ustawianie prototypu. Weź ten sam kod, ale Usuń prototyp./ align = "left" / linear Czy coś się zmieniło? Nie. Teraz dokonaj następujących zmian:

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

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

I na końcu kodu testu...

alert(student1.favoriteColor);

Kolor będzie niebieski.

Zmiana prototypu.konstruktor, z mojego doświadczenia, nie robi wiele, chyba że robisz bardzo konkretne, bardzo skomplikowane rzeczy, które prawdopodobnie i tak nie są dobrą praktyką:) {]}

Edytuj: Po poszperaj trochę po sieci i eksperymentuj, wygląda na to, że ludzie ustawiają konstruktor tak, aby "wyglądał" jak rzecz, która jest konstruowana z "nowym". Myślę, że argumentowałbym, że problem z tym polega na tym, że javascript jest językiem prototypowym - nie ma czegoś takiego jak dziedziczenie. Ale większość programistów pochodzi z zaplecza programowania, które wypycha dziedziczenie jako "drogę". Więc wymyślamy różne rzeczy, aby spróbować uczynić ten prototypowy język "klasyczny" język.. takie jak Rozszerzanie "klas". Naprawdę, w podanym przez nich przykładzie, nowy uczeń jest osobą - nie "rozszerza" się od innego ucznia.. student jest o osobie, i niezależnie od osoby jest studentem, jak również. Rozszerz ucznia, a cokolwiek rozszerzyłeś, jest w sercu ucznia, ale jest dostosowane do Twoich potrzeb.

Crockford jest trochę szalony i nadgorliwy, ale przeczytaj poważnie niektóre z rzeczy, które napisał.. to sprawi, że spojrzysz na to zupełnie inaczej.
 10
Author: Stephen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-10 03:00:33

TLDR; nie jest to konieczne, ale prawdopodobnie pomoże w dłuższej perspektywie, a dokładniejsze jest to.

Uwaga: dużo edytowane jak moja poprzednia odpowiedź była myląco napisane i miał kilka błędów, które przegapiłem w moim pośpiechu, aby odpowiedzieć. Dzięki tym, którzy zwrócili uwagę na kilka skandalicznych błędów.

Zasadniczo jest to poprawne podklasowanie w Javascript. Kiedy podklasujemy, musimy zrobić kilka ciekawych rzeczy, aby upewnić się, że delegacja prototypowa działa poprawnie, łącznie z nadpisaniem prototype obiektu. Nadpisanie obiektu prototype zawiera constructor, więc musimy naprawić odniesienie.

Przyjrzyjmy się szybko, jak działają "klasy" w ES5.

Załóżmy, że masz funkcję konstruktora i jej 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',
}

Kiedy wywołujesz konstruktor do utworzenia instancji, powiedz Adam:

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

Słowo kluczowe new wywołane z 'Person' w zasadzie uruchomi Konstruktor Person z kilkoma dodatkowymi liniami kod:

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
 * }
 */

Jeśli console.log(adam.species), wyszukiwanie nie powiedzie się w instancji adam i odszukamy łańcuch prototypów na jego .prototype, czyli Person.prototype - i Person.prototype ma właściwość .species, więc wyszukiwanie zakończy się sukcesem na Person.prototype. Następnie loguje 'human'.

Tutaj Person.prototype.constructor poprawnie wskaże Person.

Więc teraz interesująca część, tak zwane "podklasowanie". Jeśli chcemy utworzyć klasę Student, to jest to podklasa klasy Person z dodatkowymi zmianami, musimy się upewnić, że Student.prototype.constructor wskazuje studenta na dokładność.

Nie robi tego sama. Po podklasowaniu kod wygląda tak:
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: {}

Wywołanie tutaj new Student() zwróci obiekt ze wszystkimi właściwościami, które chcemy. Tutaj, jeśli sprawdzimy eve instanceof Person, zwróci false. Jeśli spróbujemy uzyskać dostęp eve.species, zwróci undefined.

Innymi słowy, musimy połączyć delegację tak, aby eve instanceof Person zwracała true i aby instancje Student delegowały poprawnie do Student.prototype, a następnie Person.prototype.

Ale skoro nazywamy to słowem kluczowym new, pamiętasz co to wywołanie dodaje? To wywołałoby Object.create(Student.prototype), czyli tak ustaliliśmy relację delegacyjną pomiędzy Student i Student.prototype. Zauważ, że w tej chwili {[32] } jest pusta. Więc patrząc w górę .species instancja Student nie powiedzie się, ponieważ deleguje się do tylko Student.prototype, i .species właściwość nie istnieje na Student.prototype.

Kiedy przypisujemy Student.prototype do Object.create(Person.prototype), Student.prototype siebie wtedy delegaci do Person.prototype i patrząc w górę eve.species wrócą human zgodnie z oczekiwaniami. Prawdopodobnie chcielibyśmy, żeby odziedziczyła po Studencie.prototyp i osoba.prototyp. Musimy to wszystko naprawić.

/* 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);

Teraz delegacja działa, ale nadpisujemy Student.prototype Z Person.prototype. Jeśli więc wywołamy Student.prototype.constructor, wskazywałoby to na Person zamiast Student. Dlatego musimy to naprawić.

// 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

W ES5 nasza własność constructor jest referencją, która odnosi się do funkcji, którą mamy napisany z zamiarem bycia 'konstruktorem'. Poza tym, co daje nam słowo kluczowe new, konstruktor jest inaczej' zwykłą ' funkcją.

W ES6, {[9] } jest teraz wbudowana w sposób, w jaki piszemy klasy - tak jak w, jest dostarczana jako metoda, gdy deklarujemy klasę. Jest to po prostu cukier składniowy, ale zapewnia nam pewne udogodnienia, takie jak dostęp do super, gdy rozszerzamy istniejącą klasę. Tak więc powyższy kod napisalibyśmy tak:

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;
   }
}
 10
Author: bthehuman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-27 18:26:24

To ma ogromną pułapkę, że jeśli piszesz

Student.prototype.constructor = Student;

Ale jeśli był nauczyciel, którego prototypem była również osoba, a Ty napisałeś

Teacher.prototype.constructor = Teacher;

Wtedy uczeń konstruktor jest teraz nauczycielem!

Edytuj: Można tego uniknąć, upewniając się, że prototypy ucznia i nauczyciela zostały ustawione przy użyciu nowych instancji klasy Person utworzonych przy użyciu obiektu.tworzyć, jak w przykładzie Mozilli.

Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
 8
Author: James D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-27 02:59:03

Jak dotąd zamieszanie wciąż istnieje.

Podążając za oryginalnym przykładem, ponieważ masz istniejący obiekt student1 jako:

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

Załóżmy, że nie chcesz wiedzieć, jak student1 jest tworzony, po prostu chcesz inny obiekt Jak to, możesz użyć właściwości konstruktora student1 Jak:

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

Tutaj nie uda się pobrać właściwości z Student Jeśli właściwość konstruktora nie jest ustawiona. Raczej stworzy obiekt Person.

 5
Author: Mahavir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-06 21:16:09

Mam ładny przykład kodu, dlaczego tak naprawdę konieczne jest ustawienie konstruktora prototypu..

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
Author: user3877965,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-25 17:36:52

W dzisiejszych czasach nie ma potrzeby używania funkcji sugared 'classes' ani używania 'New'. Użyj literałów obiektów.

Prototyp obiektu jest już 'klasą'. Gdy definiujesz obiekt dosłowny, jest on już instancją prototypowego obiektu. Mogą one również pełnić funkcję prototypu innego obiektu, itp.

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]

Warto przeczytać :

Oparte na klasach języki obiektowe, takie jak Java i C++, są oparte na koncepcji dwóch odrębnych podmiotów: klas i przypadki.

...

Język oparty na prototypach, taki jak JavaScript, nie czyni tego rozróżnienie: po prostu ma przedmioty. Język oparty na prototypach ma pojęcie obiektu prototypowego, obiekt używany jako szablon z które, Aby uzyskać początkowe właściwości dla nowego obiektu. Każdy obiekt może określ jego własne właściwości, zarówno podczas tworzenia, jak i podczas uruchamiania. Ponadto każdy obiekt może być skojarzony jako prototyp dla innego obiekt, pozwalający na drugi obiekt do współdzielenia pierwszego obiektu właściwości

 1
Author: ucsarge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-31 01:18:17

Jest to konieczne, gdy potrzebujesz alternatywy dla toString bez monkeypatchingu:

//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
Author: Paul Sweatte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-29 15:59:59

EDIT, myliłem się. Komentowanie tekstu w ogóle nie zmienia jego zachowania. (Testowałem)


Tak, to konieczne. Kiedy robisz
Student.prototype = new Person();  

Student.prototype.constructor staje się Person. Dlatego wywołanie Student() zwróci obiekt utworzony przez Person. If you then do

Student.prototype.constructor = Student; 

Student.prototype.constructor jest resetowany z powrotem do Student. Teraz, gdy wywołujesz Student(), wykonuje Student, który wywołuje konstruktor nadrzędny Parent(), zwraca poprawnie odziedziczony obiekt. If you didn ' t reset Student.prototype.constructor przed jego wywołaniem otrzymamy obiekt, który nie będzie miał żadnych właściwości ustawionych w Student().

 0
Author: invisible bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-10 02:41:05

Dana funkcja konstruktora prostego:

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(){...}

Domyślnie (ze specyfikacji https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor), wszystkie prototypy automatycznie otrzymują właściwość o nazwie constructor, która wskazuje na funkcję, na której jest właściwością. W zależności od konstruktora, do prototypu mogą być dodawane inne właściwości i metody, co nie jest bardzo powszechną praktyką, ale nadal jest dozwolone dla rozszerzeń.

Więc po prostu odpowiadając: musimy upewnić się, że wartość w prototypie.constructor jest poprawnie ustawiony tak, jak powinien być określony przez specyfikację.

Czy musimy zawsze ustawiać poprawnie tę wartość? Pomaga w debugowaniu i sprawia, że struktura wewnętrzna jest spójna ze specyfikacją. Powinniśmy zdecydowanie, kiedy nasze API jest używane przez strony trzecie, ale nie tak naprawdę, gdy kod jest ostatecznie wykonywany w trybie runtime.

 0
Author: kospiotr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-12 23:27:54

To nie jest konieczne. Jest to tylko jedna z wielu rzeczy tradycyjnych, mistrzowie OOP zrobić, aby spróbować włączyć JavaScript prototypowego dziedziczenia do klasycznego dziedziczenia. Jedyną rzeczą, która jest następująca

Student.prototype.constructor = Student; 

Czy, jest to, że masz teraz odniesienie do obecnego "konstruktora".

W odpowiedzi Wayne ' a, która została oznaczona jako poprawna, możesz dokładnie to samo, co poniższy kod

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

Z poniższym kodem (wystarczy zastąpić to.konstruktor z Osoba)

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

Dzięki Bogu, że przy dziedziczeniu klasycznym ES6 puryści mogą używać natywnych operatorów języka, takich jak class, extends i super, a my nie musimy widzieć jak prototype.poprawki konstruktora i odnośniki rodzica.

 -1
Author: Roumelis George,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-15 12:53:12