Skip to content
Natuurondernemer
    19 lutego, 2021 by admin

    Przewodnik dla początkujących's po JavaScript's Prototype

    Przewodnik dla początkujących's po JavaScript's Prototype
    19 lutego, 2021 by admin

    Nie można zajść daleko w JavaScript nie mając do czynienia z obiektami. Są one fundamentalne dla prawie każdego aspektu języka programowania JavaScript. W rzeczywistości, nauka tworzenia obiektów jest prawdopodobnie jedną z pierwszych rzeczy, które studiowałeś, kiedy zaczynałeś. W związku z tym, aby jak najefektywniej poznać prototypy w JavaScript, zamierzamy wykorzystać naszego wewnętrznego młodszego programistę i wrócić do podstaw.

    Obiekty to pary klucz-wartość. Najczęstszym sposobem tworzenia obiektu jest użycie nawiasów klamrowych {} a właściwości i metody dodajemy do obiektu używając notacji kropkowej.

    let animal = {}animal.name = 'Leo'animal.energy = 10animal.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}animal.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}animal.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length}

    Proste. Teraz w naszej aplikacji będziemy musieli stworzyć więcej niż jedno zwierzę. Naturalnie, następnym krokiem będzie zamknięcie tej logiki wewnątrz funkcji, którą będziemy mogli wywołać za każdym razem, gdy będziemy potrzebowali utworzyć nowe zwierzę. Nazwiemy ten wzorzec Functional Instantiation, a samą funkcję nazwiemy „funkcją konstruktora”, ponieważ odpowiada ona za „skonstruowanie” nowego obiektu.

    Funkcja Instancja

    function Animal (name, energy) { let animal = {} animal.name = name animal.energy = energy animal.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount } animal.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length } animal.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length } return animal}const leo = Animal('Leo', 7)const snoop = Animal('Snoop', 10)

    "I thought this was an Advanced JavaScript course...?" - Your brain Tak jest. Dojdziemy do tego.

    Teraz, gdy chcemy stworzyć nowe zwierzę (lub szerzej mówiąc nową „instancję”), wszystko, co musimy zrobić, to wywołać naszą Animal funkcję, przekazując jej zwierzę name i energy poziom. Działa to świetnie i jest niesamowicie proste. Jednak, czy potrafisz dostrzec jakieś słabe punkty tego wzorca? Największa z nich i ta, którą postaramy się rozwiązać, dotyczy trzech metod – eatsleep oraz play. Każda z tych metod jest nie tylko dynamiczna, ale również całkowicie generyczna. Oznacza to, że nie ma powodu, aby ponownie tworzyć te metody, tak jak robimy to obecnie, gdy tworzymy nowe zwierzę. Marnujemy tylko pamięć i sprawiamy, że każdy obiekt zwierzęcia jest większy niż powinien. Czy możesz wymyślić jakieś rozwiązanie? Co by było, gdyby zamiast tworzyć te metody od nowa za każdym razem, gdy tworzymy nowe zwierzę, przenieść je do ich własnego obiektu, a następnie każde zwierzę mogło odwoływać się do tego obiektu? Możemy nazwać ten wzorzec Functional Instantiation with Shared Methods, słowny, ale opisowy.

    Funkcjonalna instancja z metodami współdzielonymi

    const animalMethods = { eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount }, sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length }, play(length) { console.log(`${this.name} is playing.`) this.energy -= length }}function Animal (name, energy) { let animal = {} animal.name = name animal.energy = energy animal.eat = animalMethods.eat animal.sleep = animalMethods.sleep animal.play = animalMethods.play return animal}const leo = Animal('Leo', 7)const snoop = Animal('Snoop', 10)

    Przenosząc metody współdzielone do ich własnego obiektu i odwołując się do tego obiektu wewnątrz naszej Animal funkcji, rozwiązaliśmy teraz problem marnowania pamięci i zbyt dużych obiektów zwierząt.

    Object.create

    Poprawmy nasz przykład jeszcze raz, używając Object.create. Mówiąc najprościej, Object.create pozwala na stworzenie obiektu, który będzie delegował do innego obiektu w przypadku nieudanego wyszukiwania. Mówiąc inaczej, Object.create pozwala na stworzenie obiektu, który za każdym razem, gdy nie powiedzie się wyszukiwanie właściwości na tym obiekcie, może skonsultować się z innym obiektem, aby sprawdzić, czy ten inny obiekt posiada daną właściwość. To było dużo słów. Zobaczmy trochę kodu.

    const parent = { name: 'Stacey', age: 35, heritage: 'Irish'}const child = Object.create(parent)child.name = 'Ryan'child.age = 7console.log(child.name) // Ryanconsole.log(child.age) // 7console.log(child.heritage) // Irish

    Więc w powyższym przykładzie, ponieważ child został utworzony z Object.create(parent), za każdym razem, gdy wystąpi nieudane wyszukiwanie właściwości na child, JavaScript przekaże to wyszukiwanie do obiektu parent. Oznacza to, że nawet jeśli child nie ma właściwości heritageparent ma, więc kiedy zalogujesz się child.heritage otrzymasz dziedzictwo parent, które było Irish.

    Mając już Object.create w naszej szopie, jak możemy go użyć, aby uprościć nasz Animal kod z wcześniejszego kodu? Cóż, zamiast dodawać wszystkie współdzielone metody do zwierzęcia jedna po drugiej, tak jak robimy to teraz, możemy użyć Object.create do delegowania do obiektu animalMethods. Aby brzmieć naprawdę mądrze, nazwijmy to Functional Instantiation with Shared Methods and Object.create 🙃

    Funkcjonalna instancja z metodami współdzielonymi i Object.create

    const animalMethods = { eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount }, sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length }, play(length) { console.log(`${this.name} is playing.`) this.energy -= length }}function Animal (name, energy) { let animal = Object.create(animalMethods) animal.name = name animal.energy = energy return animal}const leo = Animal('Leo', 7)const snoop = Animal('Snoop', 10)leo.eat(10)snoop.play(5)

    📈 Tak więc teraz, gdy wywołamy leo.eat, JavaScript będzie szukał metody eat na obiekcie leo. To wyszukiwanie nie powiedzie się, a następnie, z powodu Object.create, deleguje się do obiektu animalMethods, w którym znajdzie eat.

    Jak na razie wszystko jest w porządku. Wciąż jednak jest kilka ulepszeń, które możemy wprowadzić. Wydaje się to trochę „hacky”, że trzeba zarządzać oddzielnym obiektem (animalMethods), aby współdzielić metody między instancjami. To wydaje się być wspólną cechą, którą chciałbyś zaimplementować w samym języku. Okazuje się, że tak i jest to powód, dla którego tu jesteś – prototype.

    Więc czym dokładnie jest prototype w JavaScript? Cóż, najprościej mówiąc, każda funkcja w JavaScript ma właściwość prototype, która odwołuje się do obiektu. Antyklimatyczne, prawda? Sprawdź to sam.

    function doThing () {}console.log(doThing.prototype) // {}

    A co jeśli zamiast tworzyć osobny obiekt do zarządzania naszymi metodami (jak to robimy z animalMethods), po prostu umieścimy każdą z tych metod na prototypie funkcji Animal? Wtedy wszystko, co musielibyśmy zrobić, to zamiast używać Object.create do delegowania do animalMethods, moglibyśmy użyć go do delegowania do Animal.prototype. Nazwiemy ten wzorzec Prototypal Instantiation.

    Prototypowa instancjacja

    function Animal (name, energy) { let animal = Object.create(Animal.prototype) animal.name = name animal.energy = energy return animal}Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length}const leo = Animal('Leo', 7)const snoop = Animal('Snoop', 10)leo.eat(10)snoop.play(5)

    👏👏👏 Mam nadzieję, że właśnie miałeś duży moment „aha”. Ponownie, prototype jest po prostu właściwością, którą posiada każda funkcja w JavaScript i jak widzieliśmy powyżej, pozwala nam dzielić metody pomiędzy wszystkie instancje funkcji. Cała nasza funkcjonalność jest wciąż taka sama, ale teraz zamiast zarządzać oddzielnym obiektem dla wszystkich metod, możemy po prostu użyć innego obiektu, który jest wbudowany w samą funkcję AnimalAnimal.prototype.

    Let’s. Go. Deeper.

    W tym momencie wiemy już trzy rzeczy:

    1. Jak stworzyć funkcję konstruktora.
    2. Jak dodać metody do prototypu funkcji konstruktora.
    3. Jak użyć Object.create do delegowania nieudanych poszukiwań do prototypu funkcji.

    Te trzy zadania wydają się być fundamentalne dla każdego języka programowania. Czy JavaScript jest naprawdę tak zły, że nie ma łatwiejszego, „wbudowanego” sposobu na osiągnięcie tego samego? Jak zapewne domyślasz się w tym momencie, istnieje, i to przy użyciu słowa kluczowego new.

    Co jest miłe w powolnym, metodycznym podejściu, które przyjęliśmy, aby dostać się tutaj, to teraz będziesz miał głębokie zrozumienie dokładnie tego, co słowo kluczowe new w JavaScript robi pod maską.

    Patrząc wstecz na nasz Animal konstruktor, dwie najważniejsze części to tworzenie obiektu i zwracanie go. Bez utworzenia obiektu za pomocą Object.create, nie moglibyśmy delegować do prototypu funkcji nieudanych poszukiwań. Bez deklaracji return, nigdy nie odzyskalibyśmy utworzonego obiektu.

    function Animal (name, energy) { let animal = Object.create(Animal.prototype) animal.name = name animal.energy = energy return animal}

    Tutaj jest fajna rzecz w new – kiedy wywołujesz funkcję używając słowa kluczowego new, te dwie linie są wykonywane dla ciebie niejawnie („pod maską”), a obiekt, który jest tworzony, nazywa się this.

    Używając komentarzy, aby pokazać, co dzieje się pod maską i zakładając, że konstruktor Animal jest wywoływany za pomocą słowa kluczowego new, można to przepisać jako to.

    function Animal (name, energy) { // const this = Object.create(Animal.prototype) this.name = name this.energy = energy // return this}const leo = new Animal('Leo', 7)const snoop = new Animal('Snoop', 10)

    i bez komentarzy „pod maską”

    function Animal (name, energy) { this.name = name this.energy = energy}Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length}const leo = new Animal('Leo', 7)const snoop = new Animal('Snoop', 10)

    Znowu powód, dla którego to działa i że

    function Animal (name, energy) { // const this = Object.create(Animal.prototype) this.name = name this.energy = energy // return this}const leo = new Animal('Leo', 7)const snoop = new Animal('Snoop', 10)

    . działa i że obiekt this został dla nas utworzony jest to, że wywołaliśmy funkcję konstruktora za pomocą słowa kluczowego new. Jeśli opuścisz new podczas wywoływania funkcji, obiekt this nigdy nie zostanie utworzony, ani nie zostanie niejawnie zwrócony. Problem z tym możemy zobaczyć na poniższym przykładzie.

    function Animal (name, energy) { this.name = name this.energy = energy}const leo = Animal('Leo', 7)console.log(leo) // undefined

    Nazwa tego wzorca to Pseudoclassical Instantiation.

    Jeśli JavaScript nie jest twoim pierwszym językiem programowania, możesz być już trochę niespokojny.

    „WTF ten koleś właśnie stworzył bardziej gównianą wersję klasy” – Ty

    Dla tych, którzy nie są zaznajomieni, klasa pozwala na stworzenie wzoru dla obiektu. Następnie za każdym razem, gdy tworzysz instancję tej klasy, otrzymujesz obiekt z właściwościami i metodami zdefiniowanymi w blueprincie.

    Sound familiar? To jest w zasadzie to, co zrobiliśmy z naszym Animal konstruktorem funkcji powyżej. Jednak zamiast używać słowa kluczowego class, po prostu użyliśmy zwykłej starej funkcji JavaScript, aby odtworzyć tę samą funkcjonalność. Przyznaję, wymagało to trochę dodatkowej pracy, jak również pewnej wiedzy na temat tego, co dzieje się „pod maską” JavaScriptu, ale wyniki są takie same.

    Tutaj jest dobra wiadomość. JavaScript nie jest martwym językiem. Jest ciągle ulepszany i uzupełniany przez komitet TC-39. Oznacza to, że nawet jeśli początkowa wersja JavaScriptu nie wspierała klas, nie ma powodu, dla którego nie mogłyby one zostać dodane do oficjalnej specyfikacji. W rzeczywistości, to jest dokładnie to, co zrobił komitet TC-39. W 2015 roku EcmaScript (oficjalna specyfikacja JavaScript) 6 została wydana ze wsparciem dla klas i słowa kluczowego class. Zobaczmy jak nasza Animal funkcja konstruktora powyżej wyglądałaby z nową składnią klas.

    class Animal { constructor(name, energy) { this.name = name this.energy = energy } eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount } sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length } play(length) { console.log(`${this.name} is playing.`) this.energy -= length }}const leo = new Animal('Leo', 7)const snoop = new Animal('Snoop', 10)

    Pretty clean, right?

    So jeśli to jest nowy sposób tworzenia klas, dlaczego spędziliśmy tyle czasu przechodząc nad starym sposobem? Powodem tego jest fakt, że nowy sposób (ze słowem kluczowym class) jest przede wszystkim tylko „cukrem składniowym” w stosunku do istniejącego sposobu, który nazwaliśmy pseudo-klasycznym wzorcem. Aby w pełni zrozumieć wygodną składnię klas ES6, musisz najpierw zrozumieć pseudo-klasyczny wzorzec.

    W tym momencie omówiliśmy podstawy prototypu JavaScript. Pozostała część tego postu będzie poświęcona zrozumieniu innych „dobrych do poznania” tematów z nim związanych. W innym poście, przyjrzymy się jak możemy wykorzystać te podstawy i użyć ich do zrozumienia jak działa dziedziczenie w JavaScript.

    Metody tablicowe

    Przed chwilą mówiliśmy szczegółowo o tym, że jeśli chcesz dzielić metody pomiędzy instancjami klasy, powinieneś umieścić te metody w prototypie klasy (lub funkcji). Możemy zobaczyć ten sam wzorzec, jeśli spojrzymy na klasę Array. Historycznie prawdopodobnie tworzyłeś swoje tablice w ten sposób

    const friends = 

    Okazuje się, że to tylko cukier nad tworzeniem new instancji klasy Array.

    const friendsWithSugar = const friendsWithoutSugar = new Array()

    Jedną z rzeczy, o których być może nigdy nie myślałeś, jest to, w jaki sposób każda instancja tablicy ma wszystkie te wbudowane metody (spliceslicepop, itp)?

    Cóż, jak teraz wiesz, to dlatego, że te metody żyją na Array.prototype i kiedy tworzysz nową instancję Array, używasz słowa kluczowego new, które ustawia delegację do Array.prototype na nieudanych lookach.

    Możemy zobaczyć wszystkie metody tablicy po prostu logując się Array.prototype.

    console.log(Array.prototype)/* concat: ƒn concat() constructor: ƒn Array() copyWithin: ƒn copyWithin() entries: ƒn entries() every: ƒn every() fill: ƒn fill() filter: ƒn filter() find: ƒn find() findIndex: ƒn findIndex() forEach: ƒn forEach() includes: ƒn includes() indexOf: ƒn indexOf() join: ƒn join() keys: ƒn keys() lastIndexOf: ƒn lastIndexOf() length: 0n map: ƒn map() pop: ƒn pop() push: ƒn push() reduce: ƒn reduce() reduceRight: ƒn reduceRight() reverse: ƒn reverse() shift: ƒn shift() slice: ƒn slice() some: ƒn some() sort: ƒn sort() splice: ƒn splice() toLocaleString: ƒn toLocaleString() toString: ƒn toString() unshift: ƒn unshift() values: ƒn values()*/

    Dokładnie ta sama logika istnieje również dla Obiektów. Wszystkie obiekty będą delegować do Object.prototype nieudane wyszukiwanie, dlatego wszystkie obiekty mają metody takie jak toString i hasOwnProperty.

    Metody statyczne

    Do tego momentu omówiliśmy dlaczego i jak dzielić metody pomiędzy instancjami klasy. Jednakże, co by się stało, gdybyśmy mieli metodę, która jest ważna dla klasy, ale nie musi być współdzielona pomiędzy instancjami? Na przykład, co by było, gdybyśmy mieli funkcję, która pobierałaby tablicę Animal instancji i określała, która z nich powinna być podana jako następna? Nazwiemy ją nextToEat.

    function nextToEat (animals) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy.name}

    Nie ma sensu, aby nextToEat żył na Animal.prototype, ponieważ nie chcemy go dzielić między wszystkie instancje. Zamiast tego możemy myśleć o tym jako o bardziej pomocniczej metodzie. Więc jeśli nextToEat nie powinien mieszkać na Animal.prototype, gdzie powinniśmy go umieścić? Cóż, oczywistą odpowiedzią jest to, że moglibyśmy po prostu przykleić nextToEat w tym samym zakresie co nasza klasa Animal a następnie odwołać się do niej, gdy jej potrzebujemy, tak jak normalnie byśmy to robili.

    class Animal { constructor(name, energy) { this.name = name this.energy = energy } eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount } sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length } play(length) { console.log(`${this.name} is playing.`) this.energy -= length }}function nextToEat (animals) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy.name}const leo = new Animal('Leo', 7)const snoop = new Animal('Snoop', 10)console.log(nextToEat()) // Leo

    Teraz to działa, ale jest lepszy sposób.

    Gdy masz metodę, która jest specyficzna dla klasy, ale nie musi być współdzielona przez wszystkie instancje tej klasy, możesz dodać ją jako static właściwość klasy.

    class Animal { constructor(name, energy) { this.name = name this.energy = energy } eat(amount) { console.log(`${this.name} is eating.`) this.energy += amount } sleep(length) { console.log(`${this.name} is sleeping.`) this.energy += length } play(length) { console.log(`${this.name} is playing.`) this.energy -= length } static nextToEat(animals) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy.name }}

    Teraz, ponieważ dodaliśmy nextToEat jako static właściwość na klasie, mieszka ona w samej klasie Animal (nie w jej prototypie) i można się do niej dostać za pomocą Animal.nextToEat.

    const leo = new Animal('Leo', 7)const snoop = new Animal('Snoop', 10)console.log(Animal.nextToEat()) // Leo

    Ponieważ podążaliśmy za podobnym wzorcem w całym tym poście, spójrzmy jak osiągnęlibyśmy to samo używając ES5. W powyższym przykładzie widzieliśmy jak użycie słowa kluczowego static spowoduje umieszczenie metody bezpośrednio w samej klasie. W ES5, ten sam wzorzec jest tak prosty, jak ręczne dodanie metody do obiektu funkcji.

    function Animal (name, energy) { this.name = name this.energy = energy}Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length}Animal.nextToEat = function (animals) { const sortedByLeastEnergy = animals.sort((a,b) => { return a.energy - b.energy }) return sortedByLeastEnergy.name}const leo = new Animal('Leo', 7)const snoop = new Animal('Snoop', 10)console.log(Animal.nextToEat()) // Leo

    Uzyskanie prototypu obiektu

    Niezależnie od tego, którego wzorca użyłeś do stworzenia obiektu, uzyskanie prototypu tego obiektu może być osiągnięte przy użyciu metody Object.getPrototypeOf.

    function Animal (name, energy) { this.name = name this.energy = energy}Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length}const leo = new Animal('Leo', 7)const prototype = Object.getPrototypeOf(leo)console.log(prototype)// {constructor: ƒ, eat: ƒ, sleep: ƒ, play: ƒ}prototype === Animal.prototype // true

    Z powyższego kodu można wyciągnąć dwa ważne wnioski.

    Po pierwsze, zauważysz, że jest obiektem z 4 metodami, constructoreatsleep, i play. To ma sens. Użyliśmy getPrototypeOf przekazując instancję, leo otrzymując z powrotem prototyp tej instancji, czyli miejsce, w którym żyją wszystkie nasze metody. To mówi nam jeszcze jedną rzecz o prototype, o której jeszcze nie mówiliśmy. Domyślnie, obiekt prototype będzie miał właściwość constructor, która wskazuje na oryginalną funkcję lub klasę, z której została utworzona instancja. Oznacza to również, że ponieważ JavaScript domyślnie umieszcza właściwość constructor na prototypie, każda instancja będzie mogła uzyskać dostęp do swojego konstruktora poprzez instance.constructor.

    Drugim ważnym wnioskiem z powyższego jest to, że Object.getPrototypeOf(leo) === Animal.prototype. To również ma sens. Funkcja konstruktora Animal ma właściwość prototypu, w której możemy dzielić metody pomiędzy wszystkie instancje, a getPrototypeOf pozwala nam zobaczyć prototyp samej instancji.

    function Animal (name, energy) { this.name = name this.energy = energy}const leo = new Animal('Leo', 7)console.log(leo.constructor) // Logs the constructor function

    Aby powiązać to, o czym rozmawialiśmy wcześniej z Object.create, powodem, dla którego to działa, jest to, że wszelkie instancje Animal będą delegować do Animal.prototype przy nieudanych lookups. Więc kiedy próbujesz uzyskać dostęp do leo.constructorleo nie ma właściwości constructor, więc będzie delegować to lookup do Animal.prototype, który rzeczywiście ma właściwość constructor. Jeśli ten akapit nie miał sensu, wróć i przeczytaj o Object.create powyżej.

    Mogłeś zobaczyć __proto__ używane wcześniej, aby uzyskać prototyp instancji. To już relikt przeszłości. Zamiast tego, użyj Object.getPrototypeOf(instance) jak widzieliśmy powyżej.

    Determinowanie czy właściwość żyje na prototypie

    Są pewne przypadki, w których musisz wiedzieć czy właściwość żyje na samej instancji czy na prototypie, do którego obiekt deleguje. Możemy to zobaczyć w akcji, przechodząc przez nasz obiekt leo, który tworzyliśmy. Załóżmy, że celem jest zapętlenie leo i zapisanie wszystkich jego kluczy i wartości. Używając pętli for in, wyglądałoby to prawdopodobnie tak.

    function Animal (name, energy) { this.name = name this.energy = energy}Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length}const leo = new Animal('Leo', 7)for(let key in leo) { console.log(`Key: ${key}. Value: ${leo}`)}

    Co spodziewałbyś się zobaczyć? Najprawdopodobniej byłoby to coś takiego –

    Key: name. Value: LeoKey: energy. Value: 7

    Jednakże to, co zobaczyłeś po uruchomieniu kodu, to było to –

    Key: name. Value: LeoKey: energy. Value: 7Key: eat. Value: function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}Key: sleep. Value: function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}Key: play. Value: function (length) { console.log(`${this.name} is playing.`) this.energy -= length}

    Dlaczego tak jest? Cóż, pętla for in będzie zapętlać wszystkie wyliczalne właściwości zarówno samego obiektu, jak i prototypu, do którego jest delegowany. Ponieważ domyślnie każda właściwość dodana do prototypu funkcji jest wyliczalna, widzimy nie tylko name i energy, ale również widzimy wszystkie metody na prototypie – eatsleep, oraz play. Aby to naprawić, musimy albo określić, że wszystkie metody prototypu są nieenumerowalne, albo potrzebujemy sposobu, aby tylko console.log, jeśli właściwość jest na samym obiekcie leo, a nie prototypie, który leo deleguje do nieudanego wyszukiwania. To właśnie tutaj hasOwnProperty może nam pomóc.

    hasOwnProperty jest właściwością na każdym obiekcie, która zwraca boolean wskazujący, czy obiekt ma określoną właściwość jako własną właściwość, a nie na prototypie, do którego obiekt deleguje. To jest dokładnie to, czego potrzebujemy. Teraz z tą nową wiedzą, możemy zmodyfikować nasz kod, aby wykorzystać hasOwnProperty wewnątrz naszej for in pętli.

    ...const leo = new Animal('Leo', 7)for(let key in leo) { if (leo.hasOwnProperty(key)) { console.log(`Key: ${key}. Value: ${leo}`) }}

    I teraz to, co widzimy, to tylko właściwości, które są na samym obiekcie leo, a nie na prototypie leo, do którego również deleguje.

    Key: name. Value: LeoKey: energy. Value: 7

    Jeśli nadal jesteś nieco zdezorientowany co do hasOwnProperty, tutaj jest trochę kodu, który może to wyjaśnić.

    function Animal (name, energy) { this.name = name this.energy = energy}Animal.prototype.eat = function (amount) { console.log(`${this.name} is eating.`) this.energy += amount}Animal.prototype.sleep = function (length) { console.log(`${this.name} is sleeping.`) this.energy += length}Animal.prototype.play = function (length) { console.log(`${this.name} is playing.`) this.energy -= length}const leo = new Animal('Leo', 7)leo.hasOwnProperty('name') // trueleo.hasOwnProperty('energy') // trueleo.hasOwnProperty('eat') // falseleo.hasOwnProperty('sleep') // falseleo.hasOwnProperty('play') // false

    Sprawdź, czy obiekt jest instancją klasy

    Czasami chcemy wiedzieć, czy obiekt jest instancją konkretnej klasy. Aby to zrobić, możesz użyć operatora instanceof. Przypadek użycia jest dość prosty, ale faktyczna składnia jest trochę dziwna, jeśli nigdy wcześniej jej nie widziałeś. Działa to tak

    object instanceof Class

    Powyższe stwierdzenie zwróci true jeśli object jest instancją Class i false jeśli nie jest. Wracając do naszego przykładu Animal mielibyśmy coś takiego.

    function Animal (name, energy) { this.name = name this.energy = energy}function User () {}const leo = new Animal('Leo', 7)leo instanceof Animal // trueleo instanceof User // false

    Sposób w jaki działa instanceof polega na tym, że sprawdza obecność constructor.prototype w łańcuchu prototypów obiektu. W powyższym przykładzie leo instanceof Animal jest true, ponieważ Object.getPrototypeOf(leo) === Animal.prototype. Ponadto, leo instanceof User jest false ponieważ Object.getPrototypeOf(leo) !== User.prototype.

    Tworzenie nowych agnostycznych funkcji konstruktora

    Czy potrafisz dostrzec błąd w poniższym kodzie?

    function Animal (name, energy) { this.name = name this.energy = energy}const leo = Animal('Leo', 7)

    Nawet wytrawni programiści JavaScriptu będą czasami potykać się na powyższym przykładzie. Ponieważ używamy pseudoclassical pattern, o którym dowiedzieliśmy się wcześniej, kiedy wywoływana jest funkcja konstruktora Animal, musimy się upewnić, że wywołujemy ją za pomocą słowa kluczowego new. Jeśli tego nie zrobimy, wtedy słowo kluczowe this nie zostanie utworzone i nie zostanie również niejawnie zwrócone.

    Przypominamy, że skomentowane linie są tym, co dzieje się za kulisami, gdy używasz słowa kluczowego new na funkcji.

    function Animal (name, energy) { // const this = Object.create(Animal.prototype) this.name = name this.energy = energy // return this}

    To wydaje się być zbyt ważnym szczegółem, aby pozostawiać go do zapamiętania innym programistom. Zakładając, że pracujemy w zespole z innymi programistami, czy istnieje sposób, w jaki moglibyśmy zapewnić, że nasz Animal konstruktor jest zawsze wywoływany za pomocą słowa kluczowego new? Okazuje się, że tak i to za pomocą operatora instanceof, o którym uczyliśmy się wcześniej.

    Jeśli konstruktor został wywołany za pomocą słowa kluczowego new, to this wewnątrz ciała konstruktora będzie instanceof samą funkcją konstruktora. To było dużo wielkich słów. Oto trochę kodu.

    function Animal (name, energy) { if (this instanceof Animal === false) { console.warn('Forgot to call Animal with the new keyword') } this.name = name this.energy = energy}

    Teraz zamiast po prostu logować ostrzeżenie do konsumenta funkcji, co jeśli ponownie wywołamy funkcję, ale tym razem ze słowem kluczowym new?

    function Animal (name, energy) { if (this instanceof Animal === false) { return new Animal(name, energy) } this.name = name this.energy = energy}

    Teraz niezależnie od tego, czy Animal zostanie wywołany ze słowem kluczowym new, nadal będzie działał poprawnie.

    Powtórzenie Object.create

    Przez cały ten post, polegaliśmy w dużej mierze na Object.create w celu tworzenia obiektów, które delegują do prototypu funkcji konstruktora. W tym momencie powinieneś już wiedzieć, jak używać Object.create w swoim kodzie, ale jedną z rzeczy, o której być może nie pomyślałeś, jest to, jak Object.create działa pod maską. Abyś mógł naprawdę zrozumieć, jak działa Object.create, sami go stworzymy. Po pierwsze, co wiemy o tym, jak działa Object.create?

    1. Przyjmuje argument, który jest obiektem.
    2. Tworzy obiekt, który deleguje do obiektu argumentu przy nieudanych wyszukiwaniach.
    3. Zwraca nowo utworzony obiekt.

    Zacznijmy od #1.

    Object.create = function (objToDelegateTo) {}

    Dosyć proste.

    Teraz #2 – musimy stworzyć obiekt, który będzie delegował do obiektu argumentu przy nieudanych wyszukiwaniach. To już jest trochę bardziej skomplikowane. Aby to zrobić, użyjemy naszej wiedzy o tym, jak słowo kluczowe new i prototypy działają w JavaScript. Najpierw, wewnątrz ciała naszej implementacji Object.create, utworzymy pustą funkcję. Następnie, ustawimy prototyp tej pustej funkcji na obiekt argumentu. Następnie, aby utworzyć nowy obiekt, wywołamy naszą pustą funkcję za pomocą słowa kluczowego new. Jeśli zwrócimy ten nowo utworzony obiekt, to również zakończy to #3.

    Object.create = function (objToDelegateTo) { function Fn(){} Fn.prototype = objToDelegateTo return new Fn()}

    Dziwne. Przejdźmy przez to.

    Gdy tworzymy nową funkcję, Fn w kodzie powyżej, przychodzi ona z właściwością prototype. Kiedy wywołujemy ją za pomocą słowa kluczowego new, wiemy, że otrzymamy obiekt, który będzie delegował do prototypu funkcji przy nieudanych próbach wyszukiwania. Jeśli nadpiszemy prototyp funkcji, wtedy możemy zdecydować, do którego obiektu delegować przy nieudanych wyszukiwaniach. Tak więc w naszym przykładzie powyżej, nadpisujemy prototyp Fn z obiektem, który został przekazany, gdy Object.create został wywołany, który nazywamy objToDelegateTo.

    Zauważ, że obsługujemy tylko jeden argument do Object.create. Oficjalna implementacja obsługuje również drugi, opcjonalny argument, który pozwala dodać więcej właściwości do utworzonego obiektu.

    Funkcje strzałkowe

    Funkcje strzałkowe nie posiadają własnego this słowa kluczowego. W rezultacie, funkcje strzałek nie mogą być funkcjami konstruktora i jeśli spróbujesz wywołać funkcję strzałki za pomocą słowa kluczowego new, wyrzuci ona błąd.

    const Animal = () => {}const leo = new Animal() // Error: Animal is not a constructor

    Ponieważ wykazaliśmy powyżej, że pseudoklasyczny wzorzec nie może być używany z funkcjami strzałek, funkcje strzałek nie mają również właściwości prototype.

    const Animal = () => {}console.log(Animal.prototype) // undefined

    Previous articleDIRECTORYNext article Gruczoły przytarczyczne: Facts, Function & Disease

    Dodaj komentarz Anuluj pisanie odpowiedzi

    Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

    Najnowsze wpisy

    • Znalezienie siebie (i innych…) w rocznikach online
    • Jak skonfigurować Bitcoin ASIC miner
    • Chris Martin ma urodziny w Disneylandzie z Dakotą Johnson
    • Co to jest teren Superfund?
    • Fishing-bait bloodworms have bee-sting bites
    • Władca Much
    • A Beginner’s Guide to Pegging
    • 42 Healthy Crockpot Soup Recipes
    • Tina Fey Biografia
    • Nike wydało 15 000 dolarów na specjalną maszynę tylko po to, aby Florida State center Michael Ojo’s shoes

    Archiwa

    • Kwiecień 2021
    • Marzec 2021
    • Luty 2021
    • Styczeń 2021
    • Grudzień 2020
    • Listopad 2020
    • Październik 2020
    • Wrzesień 2020
    • Sierpień 2020
    • Lipiec 2020
    • Czerwiec 2020
    • Maj 2020
    • Kwiecień 2020
    • DeutschDeutsch
    • NederlandsNederlands
    • EspañolEspañol
    • FrançaisFrançais
    • PortuguêsPortuguês
    • ItalianoItaliano
    • PolskiPolski

    Meta

    • Zaloguj się
    • Kanał wpisów
    • Kanał komentarzy
    • WordPress.org
    Posterity WordPress Theme