W części dotyczącej dziedziczenia w artykule MDN Wstęp do JavaScript zorientowanego obiektowo zauważyłem, że ustawili prototype.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
Czy to ma jakiś ważny cel? Czy można to pominąć?
javascript
oop
inheritance
trinth
źródło
źródło
subclass.prototype.constructor
Wskażeparent_class
, jeśli nie pisaćsubclass.prototype.constructor = subclass
; Oznacza to, żesubclass.prototype.constructor()
bezpośrednie użycie spowoduje nieoczekiwany wynik.Odpowiedzi:
Nie zawsze jest to konieczne, ale ma swoje zastosowania. Załóżmy, że chcieliśmy stworzyć metodę kopiowania w
Person
klasie bazowej . Lubię to:Co się teraz stanie, gdy utworzymy nowy
Student
i skopiujemy go?Kopia nie jest instancją
Student
. Wynika to z faktu, że (bez wyraźnych kontroli) nie moglibyśmy zwrócićStudent
kopii z klasy „podstawowej”. Możemy tylko zwrócićPerson
. Gdybyśmy jednak zresetowali konstruktor:... wtedy wszystko działa zgodnie z oczekiwaniami:
źródło
constructor
Atrybut nie ma żadnego specjalnego znaczenia w JS, więc równie dobrze możesz go wywołaćbananashake
. Jedyna różnica polega na tym, że silnik uruchamia się automatycznieconstructor
zaf.prototype
każdym razem, gdy deklarujesz funkcjęf
. Można go jednak zastąpić w dowolnym momencie.constructor
oznacza, że ma on specjalne znaczenie w JS, z definicji.return new this.constructor(this.name);
zamiastreturn new Person(this.name);
. Ponieważthis.constructor
jest toStudent
funkcja (ponieważ została ustawiona za pomocąStudent.prototype.constructor = Student;
),copy
funkcja w końcu wywołujeStudent
funkcję. Nie jestem pewien, jaki był twój zamiar z//just as bad
komentarzem.Student
konstruktor dodał dodatkowy argument, taki jakStudent(name, id)
:? Czy musimy wówczas zastąpićcopy
funkcję, wywołaćPerson
wersję z jej wnętrza, a następnie skopiować dodatkowąid
właściwość?Tak i nie.
W ES5 i wcześniejszych sam JavaScript
constructor
do niczego nie używał . Określił, że domyślny obiekt weprototype
właściwości funkcji będzie go zawierał i że będzie odwoływał się do funkcji i to było to . Nic więcej w specyfikacji tego nie dotyczyło.Zmieniło się to w ES2015 (ES6), która zaczęła używać go w odniesieniu do hierarchii dziedziczenia. Na przykład
Promise#then
używaconstructor
właściwości obietnicy, do której ją wywołujesz (przez SpeciesConstructor ), podczas budowania nowej obietnicy zwrotu. Jest także zaangażowany w tablice podtypów (przez ArraySpeciesCreate ).Poza samym językiem czasami ludzie używali go, próbując budować ogólne funkcje „klonowania” lub po prostu, gdy chcieli odwołać się do tego, co według nich byłoby funkcją konstruktora obiektu. Z mojego doświadczenia wynika, że korzystanie z niego jest rzadkie, ale czasami ludzie go używają.
Jest tam domyślnie, wystarczy go odłożyć, gdy zastąpisz obiekt we
prototype
właściwości funkcji :Jeśli tego nie zrobisz:
...
Student.prototype.constructor
dziedziczy poPerson.prototype
tym (prawdopodobnie)constructor = Person
. To jest mylące. I oczywiście, jeśli podklasujesz coś, co go używa (jakPromise
lubArray
) i nie używaszclass
¹ (który obsługuje to za Ciebie), upewnij się, że ustawiłeś to poprawnie. Więc w zasadzie: to dobry pomysł.Jest w porządku, jeśli nic w twoim kodzie (lub kodzie biblioteki, którego używasz) go nie używa. Zawsze upewniłem się, że jest prawidłowo podłączony.
Oczywiście, ze słowem kluczowym ES2015 (alias ES6)
class
, przez większość czasu byśmy go użyli, nie musimy już tego robić, ponieważ jest to obsługiwane dla nas, kiedy to robimy¹ „... jeśli podklasujesz coś, co go używa (jak
Promise
lubArray
) i nie używaszclass
...” - Można to zrobić, ale to prawdziwy ból (i trochę głupi). Musisz użyćReflect.construct
.źródło
TLDR; Nie jest to konieczne, ale prawdopodobnie pomoże na dłuższą metę i jest to bardziej dokładne.
UWAGA: Wiele zredagowałem, ponieważ moja poprzednia odpowiedź była myląco napisana i zawierała błędy, których nie spóźniłem się z odpowiedzią. Dzięki tym, którzy wskazali na pewne rażące błędy.
Zasadniczo ma to na celu prawidłowe podklasowanie w JavaScript. Kiedy dokonujemy podklasy, musimy zrobić kilka dziwnych rzeczy, aby upewnić się, że delegacja prototypowa działa poprawnie, w tym nadpisanie
prototype
obiektu. Nadpisanieprototype
obiektu obejmujeconstructor
, dlatego musimy naprawić odwołanie.Zobaczmy, jak działają „klasy” w ES5.
Powiedzmy, że masz funkcję konstruktora i jej prototyp:
Po wywołaniu konstruktora w celu utworzenia wystąpienia, powiedz
Adam
:new
Kluczowe wywołany z „osoba” w zasadzie będzie działać konstruktora osoba z kilku dodatkowych linii kodu:Gdybyśmy
console.log(adam.species)
, odnośnika zawiedzie naadam
przykład, i patrzeć prototypal łańcuch do swojego.prototype
, co jestPerson.prototype
- iPerson.prototype
ma na.species
własność, więc wyszukiwanie na udaPerson.prototype
. Następnie się zaloguje'human'
.Tutaj
Person.prototype.constructor
poprawnie wskażePerson
.Więc teraz interesująca część, tak zwane „podklasowanie”. Jeśli chcemy stworzyć
Student
klasę, która jest podklasąPerson
klasy z pewnymi dodatkowymi zmianami, musimy upewnić się, żeStudent.prototype.constructor
punkty wskazują Uczniowi na dokładność.Sam tego nie robi. Podklasę kod wygląda następująco:
Wywołanie
new Student()
tutaj zwróci obiekt ze wszystkimi pożądanymi właściwościami. Tutaj, jeśli sprawdzimyeve instanceof Person
, wrócifalse
. Jeśli spróbujemy uzyskać dostępeve.species
, nastąpi powrótundefined
.Innymi słowy, musimy uporządkować delegację, aby
eve instanceof Person
zwracała wartość true, a instancjeStudent
delegowania były poprawnie przesyłane doStudent.prototype
, a następniePerson.prototype
.ALE, ponieważ nazywamy to
new
słowem kluczowym, pamiętasz, co dodaje to wywołanie? To by zadzwoniłoObject.create(Student.prototype)
, w ten sposób ustanowiliśmy relacje delegacyjne pomiędzyStudent
iStudent.prototype
. Zauważ, że terazStudent.prototype
jest pusty. Dlatego wyszukiwanie.species
instancjiStudent
nie powiedzie się, ponieważ deleguje ją tylkoStudent.prototype
, a.species
właściwość nie istniejeStudent.prototype
.Kiedy przydzielimy
Student.prototype
doObject.create(Person.prototype)
,Student.prototype
sama deleguje się doPerson.prototype
, a wyszukiwanieeve.species
wróci,human
jak się spodziewamy. Przypuszczalnie chcielibyśmy, aby odziedziczył po Student.prototype AND Person.prototype. Musimy to wszystko naprawić.Teraz delegacja działa, ale zastępujemy
Student.prototype
jąPerson.prototype
. Więc jeśli zadzwonimyStudent.prototype.constructor
, wskazywałoby to naPerson
zamiastStudent
. To dlatego musimy go naprawić.W ES5 nasza
constructor
właściwość jest referencją odnoszącą się do funkcji, którą napisaliśmy z zamiarem bycia „konstruktorem”. Oprócz tego, conew
daje nam słowo kluczowe, konstruktor jest poza tym „prostą” funkcją.W ES6
constructor
jest teraz wbudowany w sposób, w jaki piszemy klasy - podobnie jak w, jest on dostarczany jako metoda, gdy deklarujemy klasę. Jest to po prostu cukier składniowy, ale zapewnia nam pewne udogodnienia, takie jak dostęp dosuper
kiedy rozszerzamy istniejącą klasę. Więc napisalibyśmy powyższy kod w ten sposób:źródło
eve instanceof Student
powróciłtrue
. Wyjaśnienie można znaleźć na stackoverflow.com/questions/35537995/ ... Również kiedy mówisz owhich is, at the moment, nothing
czym mówisz? Każda funkcja ma prototyp, więc jeśli sprawdzęStudent.prototype
, czy to coś.Object.create(Person.prototype)
TheStudent.prototype
jest pusty. Więc jeśli się zalogujemyeve.species
, nie deleguje się odpowiednio do swojej nadklasy Person i nie będzie się logował'human'
. Przypuszczalnie chcemy, aby każda podklasa dziedziczyła po prototypie, a także prototypie super.which is, at the moment, nothing
miałem na myśli, żeStudent.prototype
obiekt jest pusty.Student.prototype
doObject.create(Person.prototype)
- co, jeśli pamiętasz, w ten sam sposób wszystkie instancje Osoby są skonfigurowane do delegowania doPerson.prototype
- wyszukiwanie właściwości w instancjiStudent
komendy delegowanej byłoby tylkoStudent.prototype
. Więceve.species
nie powiedzie się jego wyszukiwanie. Jeśli go przypisamy, toStudent.prototype
sam się delegujePerson.prototype
, a wyszukiwanieeve.species
wrócihuman
.instance
Konstruktorem„ podklasy ”, będzie dokładna”. Nie,instanceof
nie używaconstructor
. „Jednakże, jeśli spojrzymy na konstruktora .prototype. studenta, nadal wskazywałby na Osoba” Nie, to będzieStudent
. Nie rozumiem sensu tego przykładu. Wywołanie funkcji w konstruktorze nie jest dziedziczeniem. „W ES6 konstruktor jest teraz rzeczywistą funkcją zamiast odwołania do funkcji” Uh co?Nie zgodziłbym się. Nie trzeba ustawiać prototypu. Weź ten sam kod, ale usuń linię prototype.constructor. Czy coś się zmienia? Nie. Teraz wprowadź następujące zmiany:
a na końcu kodu testowego ...
Kolor będzie niebieski.
Z mojego doświadczenia wynika, że zmiana na prototype.constructor nie robi wiele, chyba że robisz bardzo konkretne, bardzo skomplikowane rzeczy, które i tak prawdopodobnie nie są dobrą praktyką :)
Edycja: Po przeszukaniu sieci i przeprowadzeniu eksperymentów, wygląda na to, że ludzie ustawili konstruktor tak, aby „wyglądał” jak ten, który jest konstruowany za pomocą „nowego”. Chyba twierdzę, że problem polega na tym, że javascript jest językiem prototypowym - nie ma czegoś takiego jak dziedziczenie. Ale większość programistów wywodzi się ze środowiska programowania, które popycha dziedziczenie jako „drogę”. Wymyślamy różne rzeczy, aby ten prototypowy język stał się językiem „klasycznym”, na przykład rozszerzając „klasy”. Naprawdę, w podanym przez nich przykładzie, nowy uczeń to osoba - to nie „rozciąga się” od innego ucznia. Uczeń jest o tej osobie, i cokolwiek to jest. Rozszerz ucznia i cokolwiek innego
Crockford jest trochę szalony i nadgorliwy, ale poczytaj kilka rzeczy na temat tego, co napisał ... sprawi, że spojrzysz na to inaczej.
źródło
Ma to ogromną pułapkę, że jeśli napisałeś
ale jeśli byłby Nauczyciel, którego prototypem był także Osoba, a ty napisałeś
wtedy Konstruktor Ucznia jest teraz Nauczycielem!
Edycja: Możesz tego uniknąć, upewniając się, że ustawiłeś prototypy Ucznia i Nauczyciela przy użyciu nowych instancji klasy Person utworzonych za pomocą Object.create, jak w przykładzie Mozilla.
źródło
Student.prototype = Object.create(...)
zakłada się w tym pytaniu. Ta odpowiedź dodaje jedynie możliwe zamieszanie.Object.create(...)
jest używany w artykule MDN, który zrodził pytanie, ale nie w samym pytaniu. Jestem pewien, że wiele osób nie klika.Do tej pory zamieszanie wciąż istnieje.
Zgodnie z oryginalnym przykładem, ponieważ masz istniejący obiekt
student1
jako:Załóżmy, że nie chcesz wiedzieć, jak
student1
jest tworzony, po prostu chcesz inny obiekt taki jak ten, możesz użyć właściwości konstruktora,student1
np .:Tutaj nie będzie można pobrać właściwości,
Student
jeśli właściwość konstruktora nie jest ustawiona. Raczej stworzyPerson
obiekt.źródło
Mam dobry przykład kodu, dlaczego tak naprawdę konieczne jest ustawienie konstruktora prototypu ..
źródło
createNewCar
metoda polega na tworzeniu fabryk !? Wygląda na to, że powinien był zostać użytyvar audiFactory = new CarFactory("Audi")
zamiast dziedziczenia.this.constructor
wewnętrznie, więc nic dziwnego, że trzeba go ustawić. Czy masz jakiś przykład bez niego?W dzisiejszych czasach nie ma potrzeby używania „klas” funkcji słodzonych ani używania „nowych”. Użyj literałów obiektowych.
Prototyp Object jest już „klasą”. Po zdefiniowaniu literału obiektu jest to już instancja prototypowego obiektu. Mogą również działać jako prototyp innego obiektu itp.
Warto przeczytać :
źródło
Jest to konieczne, gdy potrzebujesz alternatywy dla
toString
bez monkeypatchingu:źródło
foo.constructor()
??EDYCJA, tak naprawdę się myliłem. Komentowanie wyjścia nie zmienia jego zachowania. (Przetestowałem to)
Tak, jest to konieczne. Kiedy to zrobisz
Student.prototype.constructor
staje sięPerson
. Dlatego wywołanieStudent()
zwróci obiekt utworzony przezPerson
. Jeśli to zrobiszStudent.prototype.constructor
resetuje się z powrotem doStudent
. Teraz, kiedyStudent()
go wywołujesz , wykonujeStudent
, który wywołuje konstruktor nadrzędnyParent()
, zwraca poprawnie odziedziczony obiekt. Jeśli nie zresetujesz sięStudent.prototype.constructor
przed wywołaniem go, otrzymasz obiekt, który nie będzie miał żadnej z ustawionych właściwościStudent()
.źródło
Biorąc pod uwagę prostą funkcję konstruktora:
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 konstruktor, która wskazuje na funkcję na która jest własnością. W zależności od konstruktora do prototypu mogą zostać dodane inne właściwości i metody, co nie jest bardzo powszechną praktyką, ale nadal jest dozwolone dla rozszerzeń.
Odpowiadając po prostu: musimy upewnić się, że wartość w prototype.constructor jest poprawnie ustawiona zgodnie z założeniami specyfikacji.
Czy zawsze musimy poprawnie ustawiać tę wartość? Pomaga w debugowaniu i zapewnia zgodność wewnętrznej struktury ze specyfikacją. Zdecydowanie powinniśmy, gdy nasze API jest używane przez strony trzecie, ale nie tak naprawdę, kiedy kod zostanie ostatecznie wykonany w środowisku wykonawczym.
źródło
Oto jeden przykład z MDN, który okazał się bardzo pomocny w zrozumieniu jego zastosowań.
W JavaScript mamy,
async functions
która zwraca obiekt AsyncFunction .AsyncFunction
nie jest obiektem globalnym, ale można go pobrać za pomocąconstructor
właściwości i wykorzystać.źródło
To nie jest konieczne. Jest to tylko jedna z wielu tradycyjnych rzeczy, które mistrzowie OOP robią, aby spróbować przekształcić prototypowe dziedzictwo JavaScript w klasyczne. Jedyne, co następuje
robi to, że masz teraz odniesienie do bieżącego „konstruktora”.
W odpowiedzi Wayne'a, która została oznaczona jako poprawna, możesz zrobić dokładnie to samo, co poniższy kod
z kodem poniżej (wystarczy zamienić this.constructor na Person)
Dzięki Bogu, że dzięki klasycznemu dziedziczeniu ES6 puryści mogą korzystać z natywnych operatorów języka, takich jak class, extends i super, i nie musimy widzieć jak poprawki prototype.constructor i referencje rodziców.
źródło