TypeScript ma trzy modyfikatory dostępu dla składowych klas: public (domyślny), protected i private. Określają, gdzie można użyć składowej, a kompilator zgłasza każdy dostęp z niewłaściwego miejsca.
Ostatnia linia to błąd kompilacji, ale spójrz, co wypisała, gdy mimo to została uruchomiona: 123-45-6789. To najważniejszy fakt na tej stronie, a sekcja o private go wyjaśnia.
Na co pozwala każdy modyfikator
| Modyfikator | Wewnątrz klasy | W podklasie | Z zewnątrz | Egzekwowany w czasie działania |
|---|---|---|---|---|
public (domyślny) | tak | tak | tak | nie ma czego egzekwować |
protected | tak | tak | nie | nie |
private | tak | nie | nie | nie |
#name (JavaScript) | tak | nie | nie | tak |
readonly | odczyt oraz zapis w konstruktorze | odczyt | odczyt | nie |
Modyfikatory działają na polach, metodach, getterach i setterach, konstruktorach i właściwościach parametrów (constructor(private id: string)). Pisanie public jest opcjonalne i wiele projektów je pomija.
private to sprawdzenie w czasie kompilacji
TypeScript usuwa private razem z resztą typów. Skompilowana klasa ma zwykłą właściwość, więc widzi ją wszystko, co nie przechodzi przez sprawdzanie typów: wywołania ze zwykłego JavaScriptu, JSON.stringify, Object.keys, a nawet notacja nawiasowa samego TypeScriptu, dozwolona celowo jako furtka awaryjna.
To wystarcza do tego, do czego służy private: żeby powiedzieć innym programistom (i edytorowi), że składowa jest szczegółem implementacji. Nie jest to granica bezpieczeństwa, a pole private trafi do logów i odpowiedzi JSON, jeśli sam go nie usuniesz.
Pola #private: egzekwowane w czasie działania
JavaScript ma własne pola prywatne, zapisywane z #. Egzekwuje je silnik: poza klasą obj.#field nie jest nawet poprawną składnią, a pole nie pojawia się w Object.keys, JSON.stringify ani console.log.
Napisanie s.#token poza klasą to błąd TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier) i w przeciwieństwie do private nie da się tego obejść także w czasie działania. Zasady działania w runtime opisuje strona pola prywatne w JavaScript.
private a #private: czego użyć
private x | #x | |
|---|---|---|
| Sprawdzane przez | kompilator | silnik JavaScript |
Widoczne dla Object.keys / JSON.stringify | tak | nie |
Dostęp przez nawiasy obj["x"] | dozwolony | niemożliwy |
Podklasa może zadeklarować własne x | nie, jest konflikt | tak, każda klasa ma własne #x |
Kopiowane przez spread { ...obj } | tak | nie |
| Składnia metod | private helper() | #helper() |
Użyj #private, gdy dane muszą pozostać prywatne w czasie działania (tokeny, stan wewnętrzny, którego użytkownicy biblioteki nie mogą ruszać) albo gdy chcesz, żeby JSON.stringify je pominął. Użyj private, gdy chodzi tylko o czyste publiczne API, gdy framework musi odczytać pole przez refleksję albo żeby dopasować się do istniejącego stylu projektu. Nie łącz ich: private #x to błąd TS18010 (An accessibility modifier cannot be used with a private identifier).
protected i podklasy
Składowa protected jest dostępna wewnątrz klasy i w każdej klasie, która ją rozszerza, ale nie na instancjach z zewnątrz.
Jedna reguła bywa zaskakująca. Wewnątrz Polygon możesz odczytać sides na this albo na innym obiekcie Polygon, ale nie na zwykłym Shape przekazanym jako parametr: other.sides, gdzie other: Shape, to błąd TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). Podklasa może sięgać do chronionych składowych tylko tych obiektów, które należą do jej własnej gałęzi hierarchii.
Podklasa może uczynić chronioną składową publiczną, deklarując ją ponownie, ale nie może zmienić publicznej składowej w chronioną ani prywatną. Zadeklaruj ją ponownie z inicjalizatorem (public override sides = 6) albo tylko jako typ (declare public sides: number). Samo public sides: number; zostaje odrzucone z błędami TS2564 i TS2612, bo przy nowoczesnych polach klas zresetowałoby odziedziczoną wartość do undefined po powrocie z super().
readonly
readonly blokuje ponowne przypisanie po utworzeniu obiektu. Pole można ustawić w deklaracji albo w konstruktorze; każde późniejsze przypisanie to błąd TS2540.
Widać tu dwa ograniczenia. readonly jest płytkie: tablicy nie da się podmienić, ale jej zawartość może się zmieniać (otypuj ją jako readonly string[], żeby zablokować push). Poza tym, tak jak private, znika w czasie działania, więc przypisanie odrzucone przez kompilator i tak się wykonało. Dla wartości, która nie może się zmienić w czasie działania, użyj Object.freeze albo gettera bez settera. Strona o readonly omawia Readonly<T> i tablice tylko do odczytu.
readonly łączy się z modyfikatorami dostępu: private readonly cache = new Map<string, number>() to częsty wzorzec dla pola, które jest wewnętrzne i nigdy nie jest przypisywane ponownie.
Częste błędy
- Traktowanie
privatejako zabezpieczenia. W czasie działania go nie ma. Dla danych, które muszą pozostać ukryte, użyj#fieldi nigdy nie przekazuj obiektu z sekretami prosto doJSON.stringify. - Pisanie
publicwszędzie. To wartość domyślna; dodanie jej niczego nie zmienia. - Używanie
protecteddo wszystkiego, co „wewnętrzne”. Jeśli żadna podklasa tego nie potrzebuje,privatezmniejsza publiczną powierzchnię. - Oczekiwanie, że
readonlyzamrozi zagnieżdżone dane. Blokuje tylko ponowne przypisanie samej właściwości.
Najczęściej zadawane pytania
Jakie modyfikatory dostępu są w TypeScript?
public (domyślny: dostępny wszędzie), protected (wewnątrz klasy i jej podklas) i private (tylko wewnątrz klasy). readonly to osobny modyfikator, który blokuje ponowne przypisanie po konstruktorze i łączy się z każdym z tych trzech.
Czym różni się private od #private w TypeScript?
private sprawdza tylko kompilator; wyemitowany JavaScript ma zwykłą właściwość, więc obj["secret"], JSON.stringify i Object.keys nadal ją widzą. #secret to prywatne pole JavaScript: egzekwuje je środowisko uruchomieniowe, a kod spoza klasy w ogóle nie może go odczytać.
Czy private w TypeScript jest naprawdę prywatne?
Tylko w czasie kompilacji. Po kompilacji pole jest zwykłą właściwością, którą może odczytać dowolny kod JavaScript. TypeScript pozwala nawet na dostęp przez nawiasy kwadratowe (obj["field"]) do prywatnych składowych jako furtkę awaryjną. Użyj #field, gdy prywatność musi obowiązywać w czasie działania.
Czym różni się protected od private w TypeScript?
Składowa private jest widoczna tylko w klasie, która ją deklaruje. Składowa protected jest widoczna także w podklasach. Do żadnej z nich nie da się sięgnąć przez instancję spoza hierarchii klas.
Czy właściwości readonly można zmienić w TypeScript?
Właściwość readonly można przypisać w deklaracji albo w konstruktorze i nigdzie indziej (w przeciwnym razie TS2540). Jest płytka: readonly tags: string[] nie da się podmienić, ale tags.push() nadal działa. Użyj readonly string[], żeby to też zablokować.