Dlaczego odradza się stosowanie konstruktorów podczas tworzenia prototypów?

10

Szybkie tło: W JavaScript funkcja konstruktora dla każdego typu obiektu ma prototypewłaściwość. prototypeOdnosi się do obiektu, co wykonane zastosowania obiektu jako Następnym krokiem w łańcuchu prototypu. Jeśli chcesz, aby jeden typ był nieodłączny od innego typu, możesz ustawić prototypetyp podrzędny na nową instancję typu nadrzędnego.

Na przykład:

var Parent = function() { /* constructor business */ }
Parent.prototype.parentProp = "some parent property";

var Child = function() { /* constructor business */ }
Child.prototype = /*** !! Some prototype object goes here !! ***/

Moje pytanie dotyczy tego, jaki kod powinien znaleźć się w miejscu „ Some prototype object goes here” w powyższym kodzie. Moim pierwszym instynktem jest zbudowanie instancji nadrzędnej (tj. new Parent()), Ale w komentarzu do odpowiedzi na pytanie Czy to bezpieczny sposób kopiowania prototypu jednego obiektu na inny? , jeden użytkownik pisze:

Nie, nie używaj new bar()dla obiektu prototypowego!

(... co jest opinią, którą widziałem w wielu SO odpowiedziach i komentarzach, ale jest to jedyny przykład, jaki mam w tej chwili pod ręką.)

Inną opcją jest użycie Object.create(Parent.prototype)jako Child.prototype. O ile mi wiadomo, tworzy to również nową Parentinstancję, ale nie uruchamia Parentkonstruktora.

Czy ktoś może wyjaśnić, dlaczego należy unikać uruchamiania funkcji konstruktora podczas generowania obiektu prototypowego z typu nadrzędnego? Czy pojawia się jakiś istotny problem techniczny (być może z wieloma poziomami dziedziczenia)? A może taki wzorzec jest niewłaściwym użyciem konstruktorów, które kolidują z pewnymi najlepszymi praktykami prototypowymi (np. Uruchamianie konstruktora podczas tworzenia prototypu narusza pewne rozdzielenie obaw)?

apsillery
źródło

Odpowiedzi:

5

Ponieważ jest to funkcja, której nie trzeba wywoływać. newnie różni się niczym od zwykłego wywołania funkcji w JavaScript.

Konstruktor może zrobić coś więcej niż ustawić pola. Na przykład, jeśli sprawdza poprawność danych przychodzących, spowoduje to błąd sprawdzania poprawności, gdy próbujesz po prostu ustawić łańcuch dziedziczenia.

I nie potrzebujesz Object.create, to wystarczy:

function objectCreate( proto ) {
    function T(){}
    T.prototype = proto;
    return new T();
}
Esailija
źródło
Ale dokładnie tak Object.createjest zaimplementowane.
Casey Chu,
@CaseyChu wcale. Wspomniałem o tym, ponieważ w łączonym poście ktoś powiedział „ Object.createnie działa w IE8” - co jest po prostu bezużytecznym komentarzem, gdy można go zaimplementować w tym przypadku użycia w 2 sekundy w dowolnej przeglądarce.
Esailija
2

Teraz, gdy moje rozumienie się nieco poszerzyło, chciałbym skorzystać z odpowiedzi Esailii na konkretnym przykładzie:

Jednym szczególnym problemem jest to, że konstruktor może ustawiać właściwości specyficzne dla instancji. Zatem jeśli użyjesz utworzonego obiektu prototypowego new, wszystkie twoje instancje potomne mogłyby współdzielić jedną właściwość określoną przez konstruktora z prototypu.

Załóżmy na przykład, że Parentkażda instancja ma unikalną idwłaściwość ustawioną jako czas budowy:

var Parent = function() { this.id = Math.random(); }
Parent.prototype.parentProp = "some parent property";

var Child = function() { /* constructor business */ }
Child.prototype = new Parent();

Spowoduje to, że wszystkie Child instancje będą dzielić pojedynczą idwłaściwość prototypową odziedziczoną po jednym i jedynym new Parent()obiekcie używanym jako Child.prototype.

Lepszym podejściem w tym przypadku jest użycie Object.createi bezpośrednie wywołanie konstruktora nadrzędnego (w razie potrzeby) wewnątrz konstruktora podrzędnego:

var Parent = function() { this.id = Math.random(); }
Parent.prototype.parentProp = "some parent property";

var Child = function() {
    // run `this` through the Parent constructor function
    Parent.apply(this, arguments);
}
Child.prototype = Object.create(Parent.prototype);
apsillery
źródło