Czy private w TypeScript jest naprawdę private? A jeśli nie, to jak zrobić, żeby private pozostał całkowicie private?
Na początek rzućmy okiem na taki oto kodzik:
class Padawan {private midichlorianLevel = Math.random()constructor(public name: string){}}const anakin = new Padawan("Anakin");anakin.name // "Anakin"anakin.midichlorianLevel // Error: Property 'midichlorianLevel' is private and only accessible within class 'Padawan'.
Całkiem OK. Mniej więcej tego oczekujemy od enkapsulacji per se. Zanim przejdziemy jednak dalej, zobaczmy, jak wygląda taki kod po przepisaniu na JS:
"use strict";class Padawan {constructor(name) {this.name = name;this.midichlorianLevel = Math.rand();}}const anakin = new Padawan("Anakin");anakin.name; // Anakinanakin.midichlorianLevel; // 0.9999... 😬
I już nie jest tak różowo. Nasze prywatne pole już dłużej nie jest prywatne. Każdy może się do niego dostać i korzystać z pominięciem API danej klasy. No dobra, TS to wciąż tylko superset JavaScriptu, więc jesteśmy w stanie sobie to wytłumaczyć i przymknąć oko. Bo przecież skoro kompilator wyrzuci nam błąd to nie użyjemy pola anakin.midichlorianLevel
z premedytacją, prawda?
Ale, niestety, problemów jest więcej:
const jarjar = new Padawan("JarJar");console.log(JSON.stringify(jarjar))
Taki zapis spowoduje przy wywołaniu poniższy output:
{"name":"jarjar","midichlorianLevel":"0.000000001"}
😬Nasza klasa jest przeniesiona ze wszystkimi jej polami do formatu JSON. Nawet z tymi prywatnymi. W efekcie czego zapisujemy dosłownie cały stan naszej instancji. Pewnie nie zawsze będzie nam to przeszkadzało, ale jak w takim razie to obejść? Jak sprawić by pole private
było private !important
? 😉
Z pomocą przychodzi wprowadzony do JavaScript znak specjalny: #
, sprawdźmy jeszcze raz ten sam przykład posiłkując się właśnie tym symbolem:
class Padawan {#midichlorianLevel = Math.rand()constructor(public name: string){}}const anakin = new Padawan("Anakin");anakin.name // "Anakin"anakin.midichlorianLevel // Error: Property '#midichlorianLevel' is private and only accessible within class 'Padawan'.console.log(JSON.stringify(a)) // {"name":"Anakin"}
Zamieniliśmy deklarację pola prywatnego znaną z TS na tę znaną z JS. Kompilator jednak zachowuje się w sposób jak najbardziej poprawny - pole #midichlorianLevel
(bo tak należy się do niego odnosić również wewnątrz klasy) nie jest dostępne poza klasą Padawan
. Poprawnie! Output z JSON.stringify
również jest poprawny - otrzymaliśmy tylko jedno pole w obiekcie JSON. Wszystko się zgadza.
Ktoś może powiedzieć, że to granie na kodach, że nie wolno tak żonglować TypeScriptem i JavaScriptem w jednym projekcie. Prawda jest taka, że TS przyswoił sobie tę nomenklaturę, nazywając ją nawet hard privacy
i respektuje jej użycie od wersji 3.8
. Podpowiadając nawet, kiedy należy skorzystać z tego obejścia. Korzystajcie więc, jeśli tylko Wasza potrzeba projektowa tego wymaga, by ukryć coś ze swoich klas, ale tym razem już tak naprawdę.
Zdjęcie Dayne Topkin z Unsplash.