Weź te 2 przykłady:
var A = function() { this.hey = function() { alert('from A') } };
vs.
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
Większość osób (szczególnie najwyżej oceniane odpowiedzi) próbowała wyjaśnić, czym się różnią, nie wyjaśniając DLACZEGO. Myślę, że to źle i jeśli najpierw zrozumiesz podstawy, różnica stanie się oczywista. Spróbujmy najpierw wyjaśnić podstawy ...
a) Funkcja jest obiektem w JavaScript. KAŻDY obiekt w JavaScript otrzymuje właściwość wewnętrzną (co oznacza, że nie możesz uzyskać do niej dostępu tak jak inne właściwości, z wyjątkiem być może przeglądarek takich jak Chrome), często nazywanych __proto__
(możesz tak naprawdę wpisać anyObject.__proto__
w Chrome, aby zobaczyć, do czego się odwołuje). , właściwość, nic więcej. Właściwość w JavaScript = zmienna wewnątrz obiektu, nic więcej. Co robią zmienne? Wskazują na rzeczy.
Więc na co wskazuje ta __proto__
właściwość? Cóż, zwykle inny przedmiot (wyjaśnimy później). Jedynym sposobem wymuszenia JavaScript dla __proto__
właściwości NIE wskazywania innego obiektu jest użycie var newObj = Object.create(null)
. Nawet jeśli to zrobisz, __proto__
właściwość STILL istnieje jako właściwość obiektu, po prostu nie wskazuje innego obiektu, wskazuje null
.
Oto, gdzie większość ludzi się myli:
Kiedy tworzysz nową funkcję w JavaScript (która jest również obiektem, pamiętasz?), W momencie jej zdefiniowania JavaScript automatycznie tworzy nową właściwość dla tej funkcji o nazwie prototype
. Spróbuj:
var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined
A.prototype
jest CAŁKOWICIE RÓŻNY od __proto__
nieruchomości. W naszym przykładzie „A” ma teraz DWIE właściwości zwane „prototypem” i __proto__
. To duże zamieszanie dla ludzi. prototype
a __proto__
właściwości nie są w żaden sposób powiązane, są oddzielnymi rzeczami wskazującymi na osobne wartości.
Możesz się zastanawiać: dlaczego JavaScript ma __proto__
właściwość utworzoną na każdym obiekcie? Cóż, jedno słowo: delegacja . Kiedy wywołujesz właściwość obiektu, a obiekt go nie ma, JavaScript szuka obiektu, do którego się odwołuje, __proto__
aby sprawdzić, czy może go mieć. Jeśli go nie ma, sprawdza __proto__
właściwość tego obiektu i tak dalej ... aż do końca łańcucha. Stąd nazwa łańcucha prototypów . Oczywiście, jeśli __proto__
nie wskazuje na obiekt, a zamiast tego wskazuje na null
szczęście, JavaScript zdaje sobie z tego sprawę i zwróci ci undefined
własność.
Możesz się również zastanawiać, dlaczego JavaScript tworzy właściwość wywoływaną prototype
dla funkcji podczas jej definiowania? Ponieważ próbuje cię oszukać, tak oszuka cię , że działa jak języki oparte na klasach.
Przejdźmy do naszego przykładu i stwórzmy „obiekt” z A
:
var a1 = new A();
Coś się dzieje w tle, kiedy to się stało. a1
jest zwykłą zmienną, której przypisano nowy, pusty obiekt.
Fakt, że użyłeś operatora new
przed wywołaniem funkcji, A()
zrobił coś DODATKOWEGO w tle. Słowo new
kluczowe utworzyło nowy obiekt, który teraz się odwołuje, a1
a ten obiekt jest pusty. Oto, co dzieje się dodatkowo:
Powiedzieliśmy, że w każdej definicji funkcji jest utworzona nowa właściwość o nazwie prototype
(do której można uzyskać do niej dostęp, w przeciwieństwie do tej __proto__
właściwości)? Cóż, ta właściwość jest teraz używana.
Jesteśmy teraz w punkcie, w którym mamy świeżo upieczony pusty a1
przedmiot. Powiedzieliśmy, że wszystkie obiekty w JavaScript mają wewnętrzną __proto__
właściwość, która wskazuje na coś ( a1
również to ma), czy to null, czy inny obiekt. Co new
operator nie jest to, że ustawia tę __proto__
właściwość, aby wskazywała funkcję w prototype
nieruchomości. Przeczytaj to jeszcze raz. Zasadniczo jest to:
a1.__proto__ = A.prototype;
Powiedzieliśmy, że A.prototype
to nic więcej niż pusty obiekt (chyba że zmienimy go na coś innego przed zdefiniowaniem a1
). Więc teraz w zasadzie a1.__proto__
wskazuje na to samo A.prototype
, co ten pusty obiekt. Oba wskazują na ten sam obiekt, który został utworzony, gdy ta linia się wydarzyła:
A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}
Teraz podczas var a1 = new A()
przetwarzania instrukcji dzieje się coś innego . Zasadniczo A()
jest wykonywany, a jeśli A jest coś takiego:
var A = function() { this.hey = function() { alert('from A') } };
Wszystkie te rzeczy w środku function() { }
zostaną wykonane. Gdy dojdziesz do this.hey..
linii, this
zmieni się na a1
i otrzymasz:
a1.hey = function() { alert('from A') }
Nie będę wyjaśniać, dlaczego this
wprowadzono zmiany, a1
ale jest to świetna odpowiedź, aby dowiedzieć się więcej.
Podsumowując, kiedy to zrobisz, var a1 = new A()
w tle dzieją się 3 rzeczy:
- Całkowicie nowy pusty obiekt jest tworzony i przypisywany do
a1
.a1 = {}
a1.__proto__
właściwość jest przypisana do wskazywania tego samego, co A.prototype
wskazuje na (inny pusty obiekt {})
Funkcja A()
jest wykonywana z this
ustawionym nowym, pustym obiektem utworzonym w kroku 1 (przeczytaj odpowiedź, do której się odwoływałem powyżej, dlaczego this
wprowadzono zmiany a1
)
Teraz spróbujmy stworzyć inny obiekt:
var a2 = new A();
Kroki 1, 2, 3 powtórzą się. Czy coś zauważyłeś? Kluczowym słowem jest powtórzenie. Krok 1: a2
będzie nowym pustym obiektem, krok 2: jego __proto__
właściwość wskaże tę samą rzecz, A.prototype
a co najważniejsze, krok 3: funkcja A()
zostanie PONOWNIE wykonana, co oznacza, że a2
otrzyma hey
właściwość zawierającą funkcję. a1
i a2
nazwijmy dwie ODDZIELNE właściwości, hey
które wskazują na 2 ODDZIELNE funkcje! Mamy teraz zduplikowane funkcje w tych samych dwóch różnych obiektach, które robią to samo, ups ... Możesz sobie wyobrazić implikacje pamięciowe, jeśli utworzymy 1000 obiektów new A
, po tym, jak deklaracje wszystkich funkcji zajmują więcej pamięci niż coś takiego jak liczba 2. Więc jak temu zapobiec?
Pamiętasz, dlaczego __proto__
właściwość istnieje na każdym obiekcie? Jeśli więc pobierzesz yoMan
właściwość a1
(która nie istnieje), __proto__
zostanie sprawdzona jej właściwość, która, jeśli jest to obiekt (i w większości przypadków tak jest), sprawdzi, czy zawiera yoMan
, a jeśli nie, sprawdzi ten obiekt __proto__
itp. Jeśli to zrobi, weźmie tę wartość właściwości i wyświetli ją.
Więc ktoś postanowił użyć tego faktu + faktu, że podczas tworzenia a1
jego __proto__
właściwość wskazuje na ten sam (pusty) obiekt A.prototype
i robi to:
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
Fajne! Teraz, kiedy tworzysz a1
, ponownie przechodzi przez wszystkie 3 powyższe kroki, aw kroku 3 nic nie robi, ponieważ function A()
nie ma nic do wykonania. A jeśli zrobimy:
a1.hey
Zobaczy, że a1
nie zawiera, hey
i sprawdzi swój __proto__
obiekt właściwości, aby zobaczyć, czy go ma, co ma miejsce.
Dzięki takiemu podejściu eliminujemy część z kroku 3, w której funkcje są powielane przy każdym tworzeniu nowego obiektu. Zamiast a1
i a2
posiadające odrębną hey
własność, teraz żaden z nich nie ma. Co, jak sądzę, do tej pory sam się zorientowałeś. To miła rzecz ... jeśli zrozumiesz, __proto__
i Function.prototype
takie pytania będą dość oczywiste.
UWAGA: Niektóre osoby zwykle nie nazywają wewnętrznej właściwości Prototype, ponieważ __proto__
użyłem tej nazwy pocztą, aby wyraźnie odróżnić ją od Functional.prototype
właściwości jako dwie różne rzeczy.