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.prototypejest 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. prototypea __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 nullszczęście, JavaScript zdaje sobie z tego sprawę i zwróci ci undefinedwłasność.
Możesz się również zastanawiać, dlaczego JavaScript tworzy właściwość wywoływaną prototypedla 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. a1jest zwykłą zmienną, której przypisano nowy, pusty obiekt.
Fakt, że użyłeś operatora newprzed wywołaniem funkcji, A()zrobił coś DODATKOWEGO w tle. Słowo newkluczowe utworzyło nowy obiekt, który teraz się odwołuje, a1a 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 a1przedmiot. Powiedzieliśmy, że wszystkie obiekty w JavaScript mają wewnętrzną __proto__właściwość, która wskazuje na coś ( a1również to ma), czy to null, czy inny obiekt. Co newoperator nie jest to, że ustawia tę __proto__właściwość, aby wskazywała funkcję w prototypenieruchomości. Przeczytaj to jeszcze raz. Zasadniczo jest to:
a1.__proto__ = A.prototype;
Powiedzieliśmy, że A.prototypeto 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, thiszmieni się na a1i otrzymasz:
a1.hey = function() { alert('from A') }
Nie będę wyjaśniać, dlaczego thiswprowadzono zmiany, a1ale 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.prototypewskazuje na (inny pusty obiekt {})
Funkcja A()jest wykonywana z thisustawionym nowym, pustym obiektem utworzonym w kroku 1 (przeczytaj odpowiedź, do której się odwoływałem powyżej, dlaczego thiswprowadzono 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: a2będzie nowym pustym obiektem, krok 2: jego __proto__właściwość wskaże tę samą rzecz, A.prototypea co najważniejsze, krok 3: funkcja A()zostanie PONOWNIE wykonana, co oznacza, że a2otrzyma heywłaściwość zawierającą funkcję. a1i a2nazwijmy dwie ODDZIELNE właściwości, heyktó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 yoManwł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 a1jego __proto__właściwość wskazuje na ten sam (pusty) obiekt A.prototypei 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 a1nie zawiera, heyi 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 a1i a2posiadające odrębną heywł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.prototypetakie 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.prototypewłaściwości jako dwie różne rzeczy.