Menu

Modyfikatory dostępu w TypeScript: public, private, protected

TypeScript ma trzy modyfikatory dostępu, public, private i protected, oraz readonly. Zobacz, na co pozwala każdy z nich, dlaczego private w TypeScript to tylko sprawdzenie w czasie kompilacji, a pola #private z JavaScript są egzekwowane w czasie działania, i który wybrać.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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

ModyfikatorWewnątrz klasyW podklasieZ zewnątrzEgzekwowany w czasie działania
public (domyślny)taktaktaknie ma czego egzekwować
protectedtaktaknienie
privatetaknienienie
#name (JavaScript)taknienietak
readonlyodczyt oraz zapis w konstruktorzeodczytodczytnie

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 przezkompilatorsilnik JavaScript
Widoczne dla Object.keys / JSON.stringifytaknie
Dostęp przez nawiasy obj["x"]dozwolonyniemożliwy
Podklasa może zadeklarować własne xnie, jest konflikttak, każda klasa ma własne #x
Kopiowane przez spread { ...obj }taknie
Składnia metodprivate 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 private jako zabezpieczenia. W czasie działania go nie ma. Dla danych, które muszą pozostać ukryte, użyj #field i nigdy nie przekazuj obiektu z sekretami prosto do JSON.stringify.
  • Pisanie public wszędzie. To wartość domyślna; dodanie jej niczego nie zmienia.
  • Używanie protected do wszystkiego, co „wewnętrzne”. Jeśli żadna podklasa tego nie potrzebuje, private zmniejsza publiczną powierzchnię.
  • Oczekiwanie, że readonly zamrozi 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ć.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ