Prywatne pola i metody JS vs TS

Czy private w TS jest tym samym co hash w JS?

Często słyszę od programistów: “Nie po to używam TS, aby pisać w JS!” czy “Używam private w TS, po co mam to robić w JS!?""

Czy jest to tylko kosmetyka i nie ma żadnej różnicy? Nie…

“Prywatność” w JavaScript wprowadził standard:

ECMAScript 2022

#name = 0;
#getName()
get #getName();
set #setName();

TypeScript

private name:number = 0;
private getName():void

Kiedyś, aby w JS zrobić pole prywatne/metodę stosowano tzw. leading underscore przed polem, ale to tylko było dla czytelności, nie miało to żadnego technicznego zastosowania.

TypeScript jak wiemy, działa tylko do momentu transpilacji kodu, chroni nas kod na czas pisania, po transpilacji kod zamienia się na czysty JavaScript. I wszystkie interfejsy, typy, modyfikatory dostępu są usuwane.

…no, chyba że mówimy o enumach to tutaj sprawa wygląda inaczej, napisałem o tym osobny artykuł (Dlaczego unikam typów wyliczeniowych w TypeScript?).

Przykład

TypeScript

class Test {
  private fieldTS: number = 0;
  #fieldJS: string = "Testowy";

  private methodTS(): number {
    return 123;
  }

  #methodJS(): number {
    return 123;
  }
}

wynik od ECMAScript 2022

"use strict";
class Test {
  fieldTS = 0;
  #fieldJS = "Testowy";

  methodTS() {
    return 123;
  }

  #methodJS() {
    return 123;
  }
}

Można by się spodziewać, ze private po transpilacji zamieni się na pole z #, niestety tak się nie stało, wszystko zostało usunięte.

Jak widzimy na przykładzie, między innymi modyfikator private został całkowicie usunięty, pole zrobiło się publiczne i dostępne na zewnątrz, pole z hashem nawet po transpilacji zostało i mamy bezpieczeństwo typu.

Teraz gdy na wynikowym pliku zrobimy wywołanie:

"use strict";
class Test {
  fieldTS = 0;
  #fieldJS = "Testowy";

  methodTS() {
    return 123;
  }

  #methodJS() {
    return 123;
  }
}

const t = new Test();
t.fieldTS;
t.methodTS();
t.#fieldJS;
t.#methodJS();

Teraz gdy chcemy się odwołać do pola prywatnego z # dostajemy w konsoli: Uncaught SyntaxError: Private field ‘#fieldJS’ must be declared in an enclosing class

Obejście

Warto zaznaczyć, że nawet w czasie pisania możemy to w łatwy sposób obejść stosując rzutowanie klasy na any:

class Test {
  private fieldTS: number = 0;
  #fieldJS: string = "Testowy";

  private methodTS(): number {
    return 123;
  }

  #methodJS(): number {
    return 123;
  }
}
const test = new Test();

console.log((test as any).fieldTS); //0
(test as any).fieldTS = 68;
console.log((test as any)['fieldTS']);   // 68

Podsumowanie

javascript

  • zawsze prywatny,
  • bezpieczeństwo,
  • działa tylko wewnątrz klasy, świat i podklasy nie mają dostępu,
  • dobre dla bibliotek, daje dostęp do tylko publicznych metod, pól zwiększa odporność na błędne działanie biblioteki

typescript

  • może być uznawane za bardziej czytelne,
  • brak bezpieczeństwa,
  • można obejść na czas pisania stosując rzutowanie na any,
  • działa tylko do czasu transpilacji, potem jest publiczne

Od autora

Stawiam bardziej JavaScript nad TypeScript, jeśli mogę używam natywnych rozwiązań dopiero potem uzupełniam TS.

Jak w każdym narzędziu warto być ostrożnym, bo z czasem mogą przynieść odwrotne skutki do zamierzonych.

Jak na przykład dekoratory czy enumy, o których napiszę osobny artykuł.