JavaScript. Czy w obiektach metody przypisywać do ‚this’ czy do ‚prototype’?

Zdarza się, że ludzie kodujący w JS uważa, że różnica pomiędzy używaniem this a prototype podczas przypisywania metod do funkcji-konstruktorów, jest tylko kosmetyczna. Sam przez jakiś czas tak myślałem. W takim razie pada pytanie, czego używać tworząc konstruktory w javascript this czy prototype?.

W rzeczywistość, te dwa sposoby tworzenia konstruktorów dają różne rezultaty, to nie jest tylko kwestia zapisu. Postaram się przedstawić najważniejsze różnice. Warto to wiedzieć, żeby móc świadomie tworzyć programy w JS.

javascript this czy prototype

Jak stwierdził Douglas Crockford, autor książki JavaScript dobre strony, JS jest ‚językiem mało pewnym siebie’. Miał na myśli to, że te cechy, które naprawdę wyróżniają JS, nie otrzymują odpowiedniej uwagi. Na przykład, zamiast w pełni korzystać z dóbr obiektowości bazującej na prototypach, ludzie ‚tłumaczą’ zachowanie JS na mechanizmy klasycznych języków obiektowych. Cóż, przyznaję, sam to robię.

Niestety, powoduje to, że często baza wiedzy jest niekompletna i nie zdajemy sobie sprawy z tego co do końca robimy. A prawda jest taka, że JS ma wiele bardzo potężnych cech, których warto być świadomym. Pytanie na które dziś odpowiem to oczywiście tylko cząstka tej wiedzy, ale może komuś pomoże 🙂 .

Ok, po tym przydługawym wstępnie czas przejść do konkretów. Wyobraźmy sobie taką sytuację: chcę stworzyć funkcję konstruktor, której używać będę do tworzenia nowych obiektów w programie. Konstruktor ma jedną metodę. Mogę to zapisać na dwa sposoby. Tak:

lub tak:

Pozornie efekt jest taki sam. Gdy stworzymy instancję tych konstruktorów za pomocą operatora new. Można bez problemu wywoływać na nich metody hello:

Efekty są takie jak każdy by się spodziewał, wypisanie odpowiedniej wiadomości w konsoli. Wewnątrz programu jednak wywołania te różnią się od siebie. Gdy chcę dostać się do pola lub metody obiektu, wartość ta najpierw szukana jest w danym obiekcie. Prototypowa natura języka powoduje, że gdy obiekt nie posiada takiej wartości, jest ona szukana w prototypie tego obiektu. Jeżeli i tam nie zostanie znaleziona, sprawdzany jest prototyp prototypu… i tak do Object, który jest na szczycie tego łańcucha dla każdego obiektu.

W przypadku konstruktora używającego this, metoda przypisywana jest bezpośrednio do tworzonego obiektu. Oznacza to, że wywołanie jej na instancji, nie powoduje przeszukiwania łańcucha prototypów. Metoda dostępna jest wewnątrz instancji obiektu.

Jeżeli, natomiast skorzysta się z prototype, metoda nie zostanie przypisana bezpośrednio do obiektu, będzie widoczna dopiero w prototypie konstruktora. Podczas wywołania jej z instancji, będzie ona ‚pożyczana’ z prototypu obiektu nadrzędnego.

Jakie faktyczne efekty ma to na pisane programy? Na pewno pierwszy sposób powoduje, zużycie większej ilości zasobów. W końcu informacja o metodzie będzie zapisana wielokrotnie w każdej instancji. Warto to wiedzieć ale nie oszukujmy się, JS to język bardzo wysokiego poziomu i ostateczna różnica w zużyciu pamięci nie będzie miała większego znaczenia.

Jest jednak inna aspekt tej sytuacji, mający większe znaczenie dla programisty. Załóżmy, że zajdzie potrzeba zmiany działania metody ‚klasy’. Jeżeli chodzi o konkretną instancję, nie ma problemu. Wystarczy, po kropce, do odpowiedniego klucza przypisać nową wartość, czyli zmienioną treść metody. W przypadku, gdy w konstruktorze użyte zostało this, operacja nadpisze („przeładuje”) starą metodę. Jeśli natomiast programista użył prototypu, przypisanie metody bezpośrednio do obiektu spowoduje, że będzie ona dostępna w nim i program nie będzie szukał jej w łańcuchu prototypów.

Faktyczna różnica widoczna jest w momencie gdy działanie metody zmienić trzeba na wszystkich istniejących instancjach obiektu. W przypadku this, trzeba to zrobić na każdym obiekcie z osobna. Pół biedy, jeśli instancje są w jakiś sposób zgrupowane. Jeśli natomiast program nie śledzi ich stanu, zmiana metody w każdej, może okazać się trudna.

W przypadku prototype, sytuacja jest znacznie lepsza. W końcu instancje pobierają treść metody z prototypu. Wystarczy więc zmienić jej treść w jednym miejscu, w prototypie.

Oto przykład ilustrujący tę sytuację:

Specjalnie pokazałem też, że zmiana prototypu konstruktora korzystającego z this, nie przynosi żadnych efektów.

Mam nadzieję, że udało mi się trochę rozjaśnić sprawę i teraz wiadomo już czym różnią się te dwa podejścia. Oczywiście jest to zaledwie wierzchołek góry lodowej. Tylko liznąłem tutaj temat prototypów. Każdego kto interesuje się pisaniem kodu w języku JavaScript, zachęcam do zapoznania się z mechanizmem ich działania. Jest to jeden z najważniejszych elementów JSa i naprawdę trzeba wiedzieć jak funckjonuje.

Tymczasem, jak zawsze, zachęcam do polubienia mojej strony na facebooku. Zawsze na bieżąco zamieszczam tam informacje o nowościach, więc warto polubić aby nie przegapić żadnego nowego wpisu.

2 przemyślenia nt. „JavaScript. Czy w obiektach metody przypisywać do ‚this’ czy do ‚prototype’?”

  1. OK świetny tekst, dzięki za niego! 🙂

    Przy okazji jeszcze jedno pytanie mam – co jest lepsze / efektywniejsze, tworzenie nowych obiektów za pomocą konstruktorów klas (new CosTam(); ) czy za pomocą domknięć (poprzez zwracany obiekt funkcji)? Bo to dla mnie dylemat obecnie – jak tworzyć nowe obiekty.

    1. Hej. Chyba nie ma dobrej odpowiedzi na Twoje pytanie. Tak naprawdę to zależy od tego, co chcesz osiągnąć. Jeżeli dobrze rozumiem, mówiąc o tworzeniu obiektów za pomocą domknięć, masz na myśli wzorzec modułu 🙂 . Różni się on od używania operatora ‚new’ tym, że w sumie bardziej tworzysz nowy zakres niż obiekt. Z osobistego doświadczenia wiem, że w takim wypadku ciężko jest stworzyć porządną hierarchię dziedziczenia, co łatwo jest zrobić używając ‚new’. Natomiast wzorzec modułu daje Ci możliwość ‚ukrywania’ pól w zakresie, co też może być przydatne. Do tworzenia nowych obiektów, można używać też metody ‚create’ obiektu Object 🙂 . To taka ogólna odpowiedź, bo tak jak pisałem na początku, wszystko zależy od kontekstu i celu jaki chcesz osiągnąć.

Dodaj komentarz

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