Problem z podkreśleniami
Przez lata JavaScript nie miał pól prywatnych. Konwencja polegała na dodaniu podkreślenia przed nazwą właściwości i liczeniu na to, że wszyscy to uszanują:
_count wygląda na prywatne, ale takie nie jest. Każdy kod wywołujący może je odczytać, zapisać albo usunąć. Podkreślenie to uprzejma kartka na drzwiach, a same drzwi są szeroko otwarte.
Nowoczesny JavaScript rozwiązał to prawdziwymi polami prywatnymi oznaczanymi znakiem #.
# sprawia, że pole jest prywatne
Poprzedź nazwę pola znakiem # w deklaracji i w każdym miejscu, gdzie się do niego odwołujesz:
Wewnątrz klasy this.#count działa normalnie. Z zewnątrz po prostu nie istnieje:
# jest dosłownie częścią nazwy pola. To nie jest słowo kluczowe typu private znane z innych języków, tylko znak, którego parser używa do wyszukania osobnego, chronionego miejsca w obiekcie. Dlatego błąd pojawia się już podczas parsowania, zanim kod w ogóle się uruchomi.
Prywatne metody i gettery
Nie tylko pola można oznaczyć jako prywatne. Metody, gettery i settery też przyjmują prefiks #:
#assertPositive to wewnętrzna funkcja pomocnicza. Nie jest częścią publicznego API, więc uczynienie jej naprawdę prywatną oznacza, że nikt przypadkiem nie wywoła jej z zewnątrz. Nikt też nie może się od niej uzależnić, dzięki czemu możesz ją później swobodnie przemianować albo usunąć.
Prywatne składowe statyczne
Składowe statyczne też mogą być prywatne. Oznaczasz je znakiem # w ten sam sposób:
Prywatne składowe statyczne należą do samej klasy, a nie do instancji. Przydają się do liczników, cache albo konfiguracji, która nie powinna wyciekać poza klasę.
Podklasy ich nie widzą
To zaskakuje osoby przychodzące z Javy lub C#. Pola prywatne w JavaScript są prywatne dla klasy, a nie prywatne dla instancji. Podklasa nie sięgnie do prywatnych pól swojej klasy nadrzędnej:
W JavaScript nie ma protected. Jeśli podklasa potrzebuje danych, klasa nadrzędna musi udostępnić metodę, getter albo (rzadziej) pole nieprywatne. To celowa decyzja: prywatne naprawdę znaczy prywatne, a dziedziczenie nie robi w tym dziur.
Sprawdzanie pola prywatnego przez in
Czasem chcesz potwierdzić, że obiekt naprawdę należy do twojej klasy. To tak zwany brand check. Operator in działa z nazwami pól prywatnych wewnątrz klasy:
Ponieważ tylko Wallet może tworzyć obiekty z #balance, #balance in obj jest niezawodnym testem na to, że obj to prawdziwa instancja Wallet. W niektórych przypadkach brzegowych jest to szybsze i bezpieczniejsze niż instanceof, bo pól prywatnych nie da się podrobić z zewnątrz.
Częsta pułapka: zwykłe obiekty go nie mają
Pola prywatne istnieją w instancjach utworzonych przez konstruktor klasy. Jeśli spróbujesz użyć takiego pola na obiekcie, który nie powstał przez new, dostaniesz błąd:
Wywołanie metody z this, które nie jest instancją Point, rzuca błąd w czasie działania. To mechanizm stojący za brand checkiem opisanym wyżej: pola prywatne są związane z konkretną klasą, która je zadeklarowała, a nie z każdym obiektem, który przypadkiem wygląda podobnie.
Kiedy sięgać po #
Domyślnie używaj pól prywatnych zawsze, gdy fragment stanu albo funkcja pomocnicza nie jest częścią publicznego API klasy. Powody:
- Swoboda refaktoryzacji. Kod wywołujący nie może zależeć od wnętrza, którego dosłownie nie widzi.
- Prawdziwa enkapsulacja. Żadnych przypadkowych odczytów, zapisów ani usunięć z zewnątrz.
- Czystsze podpowiedzi. Edytory nie proponują prywatnych składowych kodowi z zewnątrz.
Używaj publicznych właściwości, gdy coś naprawdę jest częścią interfejsu. Użyj gettera (get name()), gdy chcesz dać dostęp tylko do odczytu do pola prywatnego. Pomiń konwencję z podkreśleniem: była obejściem luki, którą język już wypełnił.
#celsius to ukryte miejsce na dane, a celsius i fahrenheit to widoki tylko do odczytu. Kod wywołujący nie może zepsuć wewnętrznego stanu, a klasa może później swobodnie zmienić sposób przechowywania wartości.
Dalej: prototypy
Klasy to w dużej mierze lukier składniowy nad systemem prototypów JavaScript, czyli starszym i bardziej podstawowym modelem, na którym język naprawdę jest zbudowany. Zrozumienie prototypów wyjaśnia, dlaczego this zachowuje się tak, a nie inaczej, jak naprawdę działa dziedziczenie i co klasa rozszerza przez extends pod spodem. O tym jest następna strona.
Najczęściej zadawane pytania
Jak zadeklarować pole prywatne w JavaScript?
Poprzedź nazwę pola znakiem #, zarówno w deklaracji, jak i przy każdym dostępie. class Counter { #count = 0; increment() { this.#count++; } } tworzy naprawdę prywatne pole. # jest częścią nazwy, a nie operatorem.
Czym różni się #field od _field w JavaScript?
#field od _field w JavaScript?_field to tylko konwencja nazewnicza: właściwość nadal jest publiczna i każdy może ją odczytać lub zapisać. #field jest wymuszane przez język: kod spoza klasy dosłownie nie ma do niego dostępu, a próba kończy się SyntaxError już podczas parsowania. Używaj #, gdy zależy ci na prawdziwej prywatności.
Czy podklasy mają dostęp do pól prywatnych klasy nadrzędnej?
Nie. Pola prywatne należą do klasy, która je deklaruje, i nawet podklasy nie mogą ich dotknąć. Jeśli podklasa potrzebuje dostępu, klasa nadrzędna musi udostępnić metodę albo getter. To bardziej rygorystyczne niż protected w innych językach i jest zamierzone.
Czy da się sprawdzić, czy obiekt ma pole prywatne?
Tak, operatorem in wewnątrz klasy: #field in obj zwraca true, jeśli obj ma to pole prywatne. Przydaje się to do tzw. brand checków, czyli potwierdzenia, że obiekt naprawdę jest instancją twojej klasy, zanim wywołasz na nim metody.