Dlaczego mogę uzyskać dostęp do prywatnych elementów członkowskich TypeScript, kiedy nie powinienem?


108

Patrzę na implementację prywatnych członków w TypeScript i wydaje mi się to trochę zagmatwane. Intellisense nie pozwala na dostęp do prywatnego członka, ale w czystym JavaScript wszystko tam jest. To sprawia, że ​​myślę, że TS nie implementuje poprawnie prywatnych członków. jakieś pomysły?

class Test{
  private member: any = "private member";
}
alert(new Test().member);

Zastanawiasz się, dlaczego technologia IntelliSense nie daje prywatnego elementu członkowskiego w wierszu z alertem ()?
esrange

7
Nie. Zastanawiam się, dlaczego TS ma prywatny, skoro jest to tylko cukier dla inteligencji, a nie dla JavaScript, do którego się kompiluje. Ten kod wykonany na typescriptlang.org/Playground ostrzega o wartości członka prywatnego.
Sean Feldman

Jak wspomniano, musisz zadeklarować elementy jako zmienne w kontekście prywatnym, aby były prywatne. Domyślam się, że maszynopis tego nie robi, ponieważ może być nieefektywny w porównaniu do dodawania do prototypu. To również miesza z definicją typu (prywatni członkowie nie są tak naprawdę częścią klasy)
Shane,

Jeśli chcesz prawdziwych prywatnych zmiennych, które istnieją w prototypie, zajmuje to trochę czasu, ale napisałem bibliotekę o nazwie ClassJS, która robi to właśnie na GitHub: github.com/KthProg/ClassJS .
KthProg,

Odpowiedzi:


97

Podobnie jak w przypadku sprawdzania typu, prywatność członków jest wymuszana tylko w kompilatorze.

Właściwość prywatna jest zaimplementowana jako zwykła właściwość i kod spoza klasy nie ma do niej dostępu.

Aby coś było naprawdę prywatne wewnątrz klasy, nie może być członkiem klasy, byłaby to zmienna lokalna utworzona w zakresie funkcji wewnątrz kodu, który tworzy obiekt. Oznaczałoby to, że nie możesz uzyskać do niego dostępu jak członek klasy, tj. Używając thissłowa kluczowego.


25
Nie jest niczym niezwykłym, że programista javascript umieszcza zmienną lokalną w konstruktorze obiektów i używa jej jako pola prywatnego. Dziwię się, że nie wspierali czegoś takiego.
Eric

2
@Eric: Ponieważ TypeScript używa prototypu dla metod zamiast dodawać metody jako prototypy wewnątrz konstruktora, zmienna lokalna w konstruktorze nie jest dostępna z metod. Może być możliwe utworzenie zmiennej lokalnej wewnątrz opakowania funkcji dla klasy, ale nie znalazłem jeszcze sposobu, aby to zrobić. Jednak nadal byłaby to zmienna lokalna, a nie prywatny element członkowski.
Guffa

40
To jest coś, na temat czego udzielam opinii. Uważam, że powinien oferować opcję tworzenia ujawniającego wzorca modułu, aby prywatni członkowie mogli pozostać prywatni, a publiczni mogą być dostępni w JavaScript. Jest to powszechny wzorzec i zapewniłby taką samą dostępność w TS i JS.
John Papa,

Istnieje rozwiązanie, którego możesz użyć dla prywatnych statycznych członków: basarat.com/2013/03/real-private-static-class-members-in.html
basarat

1
@BasaratAli: To jest zmienna statyczna, która jest dostępna wewnątrz metod klasy, ale nie należy do klasy, tzn. Nie uzyskujesz do niej dostępu za pomocą thissłowa kluczowego.
Guffa

37

JavaScript obsługuje zmienne prywatne.

function MyClass() {
    var myPrivateVar = 3;

    this.doSomething = function() {
        return myPrivateVar++;        
    }
}

W TypeScript byłoby to wyrażone następująco:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

        this.doSomething = function () {
            return myPrivateVar++;
        }
    }
}

EDYTOWAĆ

Podejście to powinno być stosowane TYLKO RZADKO, gdy jest to absolutnie konieczne. Na przykład, jeśli chcesz tymczasowo zapisać hasło w pamięci podręcznej.

Korzystanie z tego wzorca wiąże się z kosztami wydajności (nie ma znaczenia w przypadku JavaScript ani maszynopisu) i powinno być używane tylko wtedy, gdy jest to absolutnie konieczne.


Czy maszynopis nie robi tego cały czas, ustawiając go var _thisdo użytku w funkcjach o określonym zakresie? Dlaczego miałbyś mieć skrupuły, robiąc to w zakresie zajęć?
DrSammyD

Nie. Var _to jest tylko odniesieniem do tego.
Martin

2
Dokładniej, aby nazywać je zmiennymi konstruktora, a nie prywatnymi. Nie są one widoczne w metodach prototypowych.
Roman M. Koss

1
o tak, przepraszam, że zamiast tego problem był inny, fakt, że dla każdej instancji, którą utworzysz, doSomething zostanie utworzone ponownie, ponieważ nie jest to część łańcucha prototypów.
Barbu Barbu

1
@BarbuBarbu Tak, zgadzam się. Jest to POWAŻNY problem w przypadku tego podejścia i jeden z powodów, dla których należy go unikać.
Martin

11

Gdy wsparcie dla WeakMap stanie się szerzej dostępne, istnieje interesująca technika szczegółowo opisana w przykładzie nr 3 tutaj .

Pozwala na prywatne dane ORAZ pozwala uniknąć kosztów wydajności, jak na przykład Jason Evans, umożliwiając dostęp do danych z metod prototypowych, a nie tylko metod instancji.

Połączona strona MDN WeakMap zawiera listę obsługiwanych przeglądarek w Chrome 36, Firefox 6.0, IE 11, Opera 23 i Safari 7.1.

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  decrement() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

Lubię to! Zasadniczo oznacza to ukrywanie prywatnych własności w zagregowanej klasie. Najfajniejsza będzie ... A może dodasz obsługę protectedparametrów? : D
Roman M. Koss

2
@RamtinSoltani Link do artykułu zawiera statystyki, że ze względu na to, jak działają słabe mapy, nie zapobiegnie to zbieraniu śmieci. Gdyby ktoś chciał być wyjątkowo bezpieczny podczas korzystania z tej techniki, mógłby zaimplementować własny kod usuwania, który usuwa klucz instancji klasy z każdej ze słabych map.
Ryan Thomas

1
Ze strony MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . W przeciwieństwie do tego, natywne WeakMaps przechowują „słabe” odniesienia do kluczowych obiektów, co oznacza, że ​​nie zapobiegają one usuwaniu elementów bezużytecznych w przypadku, gdyby nie było innego odniesienia do obiektu klucza. Pozwala to również uniknąć zapobiegania wyrzucaniu elementów bezużytecznych na mapie.
Ryan Thomas

@RyanThomas Prawda, to był stary komentarz, który zostawiłem jakiś czas temu. WeakMaps, w przeciwieństwie do Map, nie powoduje wycieków pamięci. Więc korzystanie z tej techniki jest bezpieczne.
Ramtin Soltani

@RamtinSoltani Więc usunąć swój stary komentarz?>
ErikE

10

Ponieważ TypeScript 3.8 zostanie wydany, będziesz mógł zadeklarować pole prywatne, do którego nie można uzyskać dostępu lub nawet wykryć je poza klasą zawierającą .

class Person {
    #name: string

    constructor(name: string) {
        this.#name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.#name}!`);
    }
}

let jeremy = new Person("Jeremy Bearimy");

jeremy.#name
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

Pola prywatne zaczynają się od #znaku

Należy pamiętać, że te prywatne pola będą inne niż pola oznaczone privatesłowem kluczowym

Nr ref. https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/


4

Podziękowania dla Seana Feldmana za link do oficjalnej dyskusji na ten temat - zobacz jego odpowiedź na link.

Przeczytałem dyskusję, z którą się łączył, i oto podsumowanie najważniejszych punktów:

  • Sugestia: prywatne własności w konstruktorze
    • problemy: brak dostępu z funkcji prototypowych
  • Sugestia: metody prywatne w konstruktorze
    • problemy: to samo, co w przypadku właściwości, dodatkowo tracisz korzyści wynikające z tworzenia funkcji raz na klasę w prototypie; zamiast tego tworzysz kopię funkcji dla każdej instancji
  • Sugestia: dodaj szablon do abstrakcyjnego dostępu do właściwości i wymuś widoczność
    • problemy: duże obciążenie wydajnościowe; TypeScript jest przeznaczony dla dużych aplikacji
  • Sugestia: TypeScript już opakowuje definicje konstruktora i metody prototypowej w zamknięciu; umieść tam prywatne metody i właściwości
    • problemy z zamknięciem własności prywatnych: stają się zmiennymi statycznymi; nie ma jednego na instancję
    • problemy z zamknięciem metod prywatnych: nie mają do nich dostępu thisbez jakiegoś obejścia
  • Sugestia: automatycznie zmieniaj prywatne nazwy zmiennych
    • kontrargumenty: to konwencja nazewnictwa, a nie konstrukcja języka. Wymieszaj to sam
  • Sugestia:@private dodaj adnotacje do metod prywatnych za pomocą minifier, które rozpoznają, że adnotacja może skutecznie zminimalizować nazwy metod
    • Brak znaczących kontrargumentów do tego

Ogólne kontrargumenty do dodania obsługi widoczności w emitowanym kodzie:

  • problem polega na tym, że sam JavaScript nie ma modyfikatorów widoczności - to nie jest problem TypeScript
  • w społeczności JavaScript istnieje już ustalony wzorzec: poprzedzaj prywatne właściwości i metody podkreśleniem, które mówi „postępuj na własne ryzyko”
  • kiedy projektanci TypeScript powiedzieli, że prawdziwie prywatne właściwości i metody nie są „możliwe”, mieli na myśli „niemożliwe w ramach naszych ograniczeń projektowych”, a konkretnie:
    • Emitowany JS jest idiomatyczny
    • Płyta kotłowa jest minimalna
    • Brak dodatkowych kosztów w porównaniu do zwykłego JS OOP

Jeśli ta odpowiedź pochodzi z tej rozmowy: typescript.codeplex.com/discussions/397651 - proszę podać link: D
Roman M. Koss

1
Tak, to jest rozmowa - ale połączyłem się z odpowiedzią Seana Feldmana na to pytanie , gdzie podaje link. Ponieważ zajął się znalezieniem linku, chciałem mu przyznać kredyt.
alexanderbird

1

W języku TypeScript funkcje prywatne są dostępne tylko wewnątrz klasy. Lubić

wprowadź opis obrazu tutaj

I pokaże błąd podczas próby uzyskania dostępu do członka prywatnego. Oto przykład:

wprowadź opis obrazu tutaj

Uwaga: będzie dobrze z javascriptem, a obie funkcje są dostępne na zewnątrz.


4
OP: „ale w czystym JavaScript, wszystko tam jest” - nie sądzę, abyś rozwiązał problem polegający na tym, że wygenerowany JavaScript ujawnia publicznie funkcje „prywatne”
alexanderbird

1
@alexanderbird Myślę, że chciał powiedzieć, że zazwyczaj wystarczy TypeScript. Kiedy programujemy w języku TypeScript, pozostajemy z tym w zakresie projektu, więc prywatność z JavaScript nie jest wielkim problemem. Przede wszystkim liczy się oryginalny kod, a nie kod transpiled (JavaScript).
Roman M. Koss

1
O ile nie piszesz i nie publikujesz biblioteki JavaScript, transpiled kod ma znaczenie
Alexanderbird

twoja odpowiedź nie jest na temat.
canbax

1

Zdaję sobie sprawę, że jest to starsza dyskusja, ale nadal może być przydatne podzielenie się moim rozwiązaniem problemu rzekomo prywatnych zmiennych i metod w TypeScript „wyciekającym” do publicznego interfejsu skompilowanej klasy JavaScript.

Dla mnie ta kwestia jest czysto kosmetyczna, tj. Chodzi o wizualny bałagan, gdy zmienna instancji jest wyświetlana w DevTools. Moją poprawką jest zgrupowanie prywatnych deklaracji razem w innej klasie, która jest następnie tworzona w klasie głównej i przypisywana do privatezmiennej (ale nadal widocznej publicznie w JS) o nazwie takiej jak __(podwójne podkreślenie).

Przykład:

class Privates {
    readonly DEFAULT_MULTIPLIER = 2;
    foo: number;
    bar: number;

    someMethod = (multiplier: number = this.DEFAULT_MULTIPLIER) => {
        return multiplier * (this.foo + this.bar);
    }

    private _class: MyClass;

    constructor(_class: MyClass) {
        this._class = _class;
    }
}

export class MyClass {
    private __: Privates = new Privates(this);

    constructor(foo: number, bar: number, baz: number) {
        // assign private property values...
        this.__.foo = foo;
        this.__.bar = bar;

        // assign public property values...
        this.baz = baz;
    }

    baz: number;

    print = () => {
        console.log(`foo=${this.__.foo}, bar=${this.__.bar}`);
        console.log(`someMethod returns ${this.__.someMethod()}`);
    }
}

let myClass = new MyClass(1, 2, 3);

Kiedy myClassinstancja jest oglądana w DevTools, zamiast widzieć wszystkich jej „prywatnych” członków wymieszanych z prawdziwie publicznymi (co może być bardzo wizualnie niechlujne w prawidłowo zrefaktorowanym prawdziwym kodzie), zobaczysz je zgrupowane w zwiniętej __właściwości:

wprowadź opis obrazu tutaj


1
Lubię to. Wygląda na czysty.

0

Oto podejście wielokrotnego użytku do dodawania odpowiednich właściwości prywatnych:

/**
 * Implements proper private properties.
 */
export class Private<K extends object, V> {

    private propMap = new WeakMap<K, V>();

    get(obj: K): V {
        return this.propMap.get(obj)!;
    }

    set(obj: K, val: V) {
        this.propMap.set(obj, val);
    }
}

Załóżmy, że masz Clientgdzieś zajęcia, które wymagają dwóch prywatnych właściwości:

  • prop1: string
  • prop2: number

Poniżej opisano, jak to wdrażasz:

// our private properties:
interface ClientPrivate {
    prop1: string;
    prop2: number;
}

// private properties for all Client instances:
const pp = new Private<Client, ClientPrivate>();

class Client {
    constructor() {
        pp.set(this, {
            prop1: 'hello',
            prop2: 123
        });
    }

    someMethod() {
        const privateProps = pp.get(this);

        const prop1 = privateProps.prop1;
        const prop2 = privateProps.prop2;
    }
}

A jeśli wszystko, czego potrzebujesz, to jedna własność prywatna, to staje się jeszcze prostsze, ponieważ ClientPrivatew takim przypadku nie musisz jej definiować .

Warto zauważyć, że w przeważającej części class Privatepo prostu oferuje ładnie czytelną sygnaturę, podczas gdy bezpośrednie użycie WeakMapnie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.