Staram się zrozumieć za kulisami JavaScript i trochę utknąłem w zrozumieniu tworzenia wbudowanych obiektów, zwłaszcza Obiektów i Funkcji oraz relacji między nimi.
Jest to skomplikowane, łatwo jest go źle zrozumieć, a wiele początkujących książek o JavaScript robi to źle, więc nie ufaj wszystkim, co czytasz.
Byłem jednym z wdrażających silnik JS Microsoftu w latach 90. i w komitecie normalizacyjnym i popełniłem wiele błędów, łącząc tę odpowiedź. (Chociaż odkąd nie pracowałem nad tym od ponad 15 lat, być może wybaczę mi.) To trudne zadanie. Ale kiedy zrozumiesz dziedziczenie prototypów, wszystko będzie miało sens.
Kiedy przeczytałem, że wszystkie wbudowane obiekty, takie jak Array, String itp., Są rozszerzeniem (dziedziczonym) z Object, założyłem, że Object jest pierwszym wbudowanym obiektem, który zostaje utworzony, a reszta obiektów dziedziczy po nim.
Zacznij od wyrzucenia wszystkiego, co wiesz o dziedziczeniu klasowym. JS używa dziedziczenia opartego na prototypach.
Następnie upewnij się, że masz bardzo jasną definicję tego, co oznacza „dziedziczenie”. Ludzie przyzwyczajeni do języków OO, takich jak C #, Java lub C ++, uważają, że dziedziczenie oznacza podtyp, ale dziedziczenie nie oznacza podtypu. Dziedziczenie oznacza, że członkowie jednej rzeczy są również członkami innej rzeczy . Nie musi to koniecznie oznaczać, że między tymi rzeczami istnieje poddziałanie! Tak wiele nieporozumień w teorii typów wynika z faktu, że ludzie nie zdają sobie sprawy z różnicy.
Ale nie ma sensu, gdy się dowiesz, że Obiekty mogą być tworzone tylko przez funkcje, ale wtedy funkcje są również niczym innym jak obiektami Funkcji.
To jest po prostu nieprawda. Niektóre obiekty nie są tworzone przez wywołanie new F
jakiejś funkcji F
. Niektóre obiekty są tworzone przez środowisko wykonawcze JS zupełnie z niczego. Są jajka, których nie złożył żaden kurczak . Zostały one utworzone przez środowisko uruchomieniowe podczas jego uruchamiania.
Powiedzmy, jakie są reguły i może to pomoże.
- Każda instancja obiektu ma obiekt prototypowy.
- W niektórych przypadkach może to być prototyp
null
.
- Jeśli uzyskujesz dostęp do elementu w instancji obiektu, a obiekt nie ma tego elementu, wówczas obiekt odkłada się na swój prototyp lub zatrzymuje się, jeśli prototyp jest zerowy.
- Element
prototype
obiektu zazwyczaj nie jest prototypem obiektu.
- Przeciwnie,
prototype
członek obiektu funkcji F jest obiekt, który będzie prototyp obiektu utworzonego przez new F()
.
- W niektórych implementacjach instancje uzyskują
__proto__
członka, który naprawdę daje swój prototyp. (To jest już przestarzałe. Nie polegaj na tym.)
- Obiekty funkcyjne otrzymują zupełnie nowy domyślny obiekt, do
prototype
którego są przypisywane podczas ich tworzenia.
- Prototyp obiektu funkcyjnego jest oczywiście
Function.prototype
.
Podsumujmy.
- Prototypem
Object
jestFunction.prototype
Object.prototype
jest obiektem prototypowym obiektu.
- Prototypem
Object.prototype
jestnull
- Prototypem
Function
jest Function.prototype
- to jedna z rzadkich sytuacji, w których Function.prototype
tak naprawdę jest prototypem Function
!
Function.prototype
jest obiektem prototypowym funkcji.
- Prototypem
Function.prototype
jestObject.prototype
Załóżmy, że tworzymy funkcję Foo.
- Prototypem
Foo
jest Function.prototype
.
Foo.prototype
jest prototypowym obiektem Foo.
- Prototypem
Foo.prototype
jest Object.prototype
.
Załóżmy, że mówimy new Foo()
- Prototyp nowego obiektu to
Foo.prototype
Upewnij się, że to ma sens. Narysujmy to. Owale są instancjami obiektowymi. Krawędzie __proto__
oznaczają albo „prototyp”, albo prototype
„ prototype
właściwość”.
Środowisko wykonawcze musi tylko utworzyć wszystkie te obiekty i odpowiednio przypisać ich różne właściwości. Jestem pewien, że widzisz, jak by to było zrobione.
Teraz spójrzmy na przykład, który sprawdza twoją wiedzę.
function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);
Co to drukuje?
Co to instanceof
znaczy? honda instanceof Car
oznacza „jest Car.prototype
równy dowolnemu obiektowi w honda
łańcuchu prototypów?”
Tak to jest. honda
Prototyp jest Car.prototype
, więc skończone. To drukuje się prawda.
A co z drugim?
honda.constructor
nie istnieje, więc konsultujemy prototyp Car.prototype
. Kiedy Car.prototype
obiekt został utworzony, automatycznie otrzymał właściwość constructor
równą Car
, więc to prawda.
A co z tym?
var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);
Co drukuje ten program?
Znów lizard instanceof Reptile
oznacza „jest Reptile.prototype
równy dowolnemu obiektowi w lizard
łańcuchu prototypów?”
Tak to jest. lizard
Prototyp jest Reptile.prototype
, więc skończone. To drukuje się prawda.
A teraz co
print(lizard.constructor == Reptile);
Możesz pomyśleć, że to również drukuje się prawdą, ponieważ lizard
został skonstruowany z, new Reptile
ale byłbyś w błędzie. Rozumuj to.
- Czy
lizard
ma constructor
nieruchomość? Nie. Dlatego patrzymy na prototyp.
- Prototypem
lizard
jest Reptile.prototype
, który jest Animal
.
- Czy
Animal
ma constructor
nieruchomość? Nie. Więc patrzymy na jego prototyp.
- Prototyp
Animal
jest Object.prototype
i Object.prototype.constructor
jest tworzony przez środowisko wykonawcze i równy Object
.
- Więc to drukuje fałsz.
Reptile.prototype.constructor = Reptile;
W pewnym momencie powinniśmy byli to powiedzieć , ale nie pamiętaliśmy!
Upewnij się, że wszystko ma dla ciebie sens. Narysuj kilka pól i strzałek, jeśli nadal są mylące.
Inną niezwykle mylącą rzeczą jest to, że jeśli console.log(Function.prototype)
drukuje funkcję, ale podczas drukowania console.log(Object.prototype)
drukuje obiekt. Dlaczego Function.prototype
funkcja, skoro miała być obiektem?
Prototyp funkcji jest zdefiniowany jako funkcja, która po wywołaniu zwraca undefined
. Wiemy już, że Function.prototype
to Function
prototyp, co dziwne. Dlatego Function.prototype()
jest to legalne, a kiedy to zrobisz, undefined
wrócisz. To jest funkcja.
Object
Prototyp nie posiada tę właściwość; to nie jest na żądanie. To tylko przedmiot.
kiedy console.log(Function.prototype.constructor)
znowu jest to funkcja.
Function.prototype.constructor
jest po prostu Function
, oczywiście. I Function
jest funkcją.
Teraz jak możesz użyć czegoś, aby stworzyć to samemu (Mind = blown).
Nadmiernie o tym myślisz . Wszystko, co jest wymagane, to aby środowisko uruchomieniowe tworzyło kilka obiektów podczas uruchamiania. Obiekty to tylko tabele wyszukiwania, które łączą ciągi z obiektami. Gdy środowisko wykonawcze uruchamia się, wszystko to musi zrobić, to utworzyć kilka obiektów tuzin puste, a następnie rozpocząć przypisywanie prototype
, __proto__
, constructor
, i tak dalej właściwości każdego obiektu, dopóki oni wykres że trzeba zrobić.
Pomocne będzie, jeśli weźmiesz ten schemat, który ci dałem powyżej, i dodasz constructor
do niego krawędzie. Szybko zobaczysz, że jest to bardzo prosty wykres obiektowy i że środowisko wykonawcze nie będzie miało problemu z jego utworzeniem.
Dobrym ćwiczeniem byłoby zrobienie tego samemu. Tutaj zacznę. Użyjemy my__proto__
w znaczeniu „obiekt prototypowy” i myprototype
„właściwości prototypowej”.
var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;
I tak dalej. Czy możesz wypełnić resztę programu, aby zbudować zestaw obiektów o tej samej topologii, co „prawdziwe” wbudowane obiekty JavaScript? Jeśli to zrobisz, przekonasz się, że jest to niezwykle łatwe.
Obiekty w JavaScript to po prostu tabele odnośników, które łączą ciągi z innymi obiektami . Otóż to! Tu nie ma magii. Wiązujesz się w węzły, ponieważ wyobrażasz sobie ograniczenia, które tak naprawdę nie istnieją, tak że każdy obiekt musiał zostać stworzony przez konstruktora.
Funkcje to tylko obiekty, które mają dodatkową możliwość: być wywoływanym. Więc przejrzyj swój mały program symulacyjny i dodaj .mycallable
właściwość do każdego obiektu, który wskazuje, czy można go wywołać, czy nie. To takie proste.
Function.prototype
może być funkcją i mieć wewnętrzne pola. Więc nie, nie wykonujesz funkcji prototypowej podczas przeglądania jej struktury. Na koniec pamiętaj, że istnieje silnik interpretujący Javascript, więc obiekt i funkcja są prawdopodobnie tworzone w silniku, a nie z Javascript i specjalnych odnośników, takich jakFunction.prototype
iObject.prototype
mogą być po prostu interpretowane w specjalny sposób przez silnik.