Warum kann ich auf private TypeScript-Mitglieder zugreifen, wenn dies nicht möglich sein sollte?

108

Ich betrachte die Implementierung privater Mitglieder in TypeScript und finde es etwas verwirrend. Intellisense erlaubt keinen Zugriff auf private Mitglieder, aber in reinem JavaScript ist alles vorhanden. Dies lässt mich denken, dass TS private Mitglieder nicht korrekt implementiert. Irgendwelche Gedanken?

class Test{
  private member: any = "private member";
}
alert(new Test().member);
Sean Feldman
quelle
Sie fragen sich, warum IntelliSense Ihnen nicht das private Mitglied in der Zeile mit der Warnung () gibt?
Esrange
7
Nee. Ich frage mich, warum TS eine private hat, wenn dies nur ein Zucker für Intellisense ist und nicht wirklich für das JavaScript, mit dem es kompiliert wird. Dieser in typescriptlang.org/Playground ausgeführte Code warnt den Wert eines privaten Mitglieds.
Sean Feldman
Wie bereits erwähnt, müssen Sie Elemente in einem privaten Kontext als Variable deklarieren, damit sie privat sind. Ich vermute, dass Typoskript dies nicht tut, weil es ineffizient sein kann, anstatt es dem Prototyp hinzuzufügen. Es spielt auch mit der Typdefinition (die privaten Mitglieder sind nicht wirklich Teil der Klasse)
Shane
Wenn Sie echte private Variablen für den Prototyp benötigen, ist der Aufwand etwas höher, aber ich habe eine Bibliothek namens ClassJS geschrieben, die genau das auf GitHub tut: github.com/KthProg/ClassJS .
KthProg

Antworten:

97

Genau wie bei der Typprüfung wird die Privatsphäre der Mitglieder nur innerhalb des Compilers durchgesetzt.

Eine private Eigenschaft wird als reguläre Eigenschaft implementiert, und Code außerhalb der Klasse darf nicht darauf zugreifen.

Um etwas innerhalb der Klasse wirklich privat zu machen, kann es kein Mitglied der Klasse sein. Es ist eine lokale Variable, die in einem Funktionsbereich innerhalb des Codes erstellt wird, der das Objekt erstellt. Das würde bedeuten, dass Sie nicht wie ein Mitglied der Klasse darauf zugreifen können, dh wenn Sie das thisSchlüsselwort verwenden.

Guffa
quelle
25
Es ist nicht ungewöhnlich, dass ein Javascript-Programmierer eine lokale Variable in einen Objektkonstruktor einfügt und sie als privates Feld verwendet. Ich bin überrascht, dass sie so etwas nicht unterstützt haben.
Eric
2
@Eric: Da TypeScript den Prototyp für Methoden verwendet, anstatt Methoden als Prototypen innerhalb des Konstruktors hinzuzufügen, ist eine lokale Variable im Konstruktor über die Methoden nicht erreichbar. Es ist möglicherweise möglich, eine lokale Variable im Funktions-Wrapper für die Klasse zu erstellen, aber ich habe noch keinen Weg gefunden, dies zu tun. Dies wäre jedoch immer noch eine lokale Variable und kein privates Mitglied.
Guffa
40
Dies ist etwas, zu dem ich Feedback gegeben habe. Ich glaube, es sollte die Möglichkeit bieten, ein aufschlussreiches Modulmuster zu erstellen, damit die privaten Mitglieder privat bleiben und die öffentlichen in JavaScript zugänglich sind. Dies ist ein allgemeines Muster und würde in TS und JS die gleiche Zugänglichkeit bieten.
John Papa
Es gibt eine Lösung, die Sie für private statische Mitglieder verwenden können: basarat.com/2013/03/real-private-static-class-members-in.html
basarat
1
@BasaratAli: Dies ist eine statische Variable, die in den Methoden der Klasse verfügbar ist, aber kein Mitglied der Klasse ist, dh Sie greifen nicht mit dem thisSchlüsselwort darauf zu.
Guffa
37

JavaScript unterstützt private Variablen.

function MyClass() {
    var myPrivateVar = 3;

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

In TypeScript würde dies folgendermaßen ausgedrückt:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

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

BEARBEITEN

Dieser Ansatz sollte nur sparsam angewendet werden, wenn dies unbedingt erforderlich ist. Zum Beispiel, wenn Sie ein Passwort vorübergehend zwischenspeichern müssen.

Die Verwendung dieses Musters verursacht Leistungskosten (unabhängig von Javascript oder Typescript) und sollte nur verwendet werden, wenn dies unbedingt erforderlich ist.

Martin
quelle
Tut Typoskript dies nicht die var _thisganze Zeit, indem es für die Verwendung in Funktionen mit Gültigkeitsbereich festgelegt wird? Warum sollten Sie Bedenken haben, dies im Klassenbereich zu tun?
DrSammyD
Nein, dies ist nur ein Hinweis darauf.
Martin
2
Genauer gesagt, sie als Konstruktorvariablen zu bezeichnen, nicht als privat. Diese sind bei Prototypmethoden nicht sichtbar.
Roman M. Koss
1
Oh ja, tut mir leid, das Problem war stattdessen ein anderes, die Tatsache, dass für jede von Ihnen erstellte Instanz doSomething erneut erstellt wird, da es nicht Teil der Prototypenkette ist.
Barbu Barbu
1
@BarbuBarbu Ja, ich stimme zu. Dies ist ein großes Problem bei diesem Ansatz und einer der Gründe, warum dies vermieden werden sollte.
Martin
11

Sobald die Unterstützung für WeakMap weiter verbreitet ist , gibt es eine interessante Technik , die in Beispiel # 3 detailliert hier .

Es ermöglicht private Daten UND vermeidet die Leistungskosten des Beispiels von Jason Evans, indem auf die Daten über Prototypmethoden anstatt nur über Instanzmethoden zugegriffen werden kann.

Auf der verknüpften MDN WeakMap-Seite wird die Browserunterstützung für Chrome 36, Firefox 6.0, IE 11, Opera 23 und Safari 7.1 aufgeführt.

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)();
    }
  }
}
Ryan Thomas
quelle
Ich mochte es! Grundsätzlich bedeutet dies, private Eigenschaften in aggregierten Klassen zu verstecken. Der größte Spaß wird sein ... Wie wäre es mit Unterstützung für protectedParameter? : D
Roman M. Koss
2
@RamtinSoltani Der verlinkte Artikel besagt, dass aufgrund der Funktionsweise von Schwachkarten die Speicherbereinigung nicht verhindert wird. Wenn jemand bei dieser Technik besonders sicher sein möchte, kann er seinen eigenen Entsorgungscode implementieren, der den Klasseninstanzschlüssel aus jeder der Schwachstellen löscht.
Ryan Thomas
1
Von der MDN-Seite: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Im Gegensatz dazu enthalten native WeakMaps "schwache" Verweise auf Schlüsselobjekte, was bedeutet, dass sie die Speicherbereinigung nicht verhindern, falls es keinen anderen Verweis auf das Schlüsselobjekt geben würde. Dadurch wird auch vermieden, dass die Speicherbereinigung von Werten in der Karte verhindert wird.
Ryan Thomas
@ RyanThomas Stimmt, das war ein alter Kommentar, den ich vor einiger Zeit hinterlassen habe. Die WeakMaps verursachen im Gegensatz zu Maps keine Speicherlecks. Es ist also sicher, diese Technik anzuwenden.
Ramtin Soltani
@RamtinSoltani Also deinen alten Kommentar löschen?>
ErikE
10

Da TypeScript 3.8 veröffentlicht wird, können Sie ein privates Feld deklarieren, auf das außerhalb der enthaltenen Klasse nicht zugegriffen oder das sogar erkannt werden kann .

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.

Private Felder beginnen mit #Zeichen

Bitte beachten Sie, dass diese privaten Felder etwas anderes sind als Felder, die mit einem privateSchlüsselwort markiert sind

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

Przemek Struciński
quelle
4

Vielen Dank an Sean Feldman für den Link zur offiziellen Diskussion zu diesem Thema - siehe seine Antwort für den Link.

Ich habe die Diskussion gelesen, mit der er verlinkt hat, und hier ist eine Zusammenfassung der wichtigsten Punkte:

  • Vorschlag: Privateigenschaften im Konstruktor
    • Probleme: Zugriff von Prototypfunktionen nicht möglich
  • Vorschlag: Private Methoden im Konstruktor
    • Probleme: Wie bei Eigenschaften, und Sie verlieren den Leistungsvorteil, eine Funktion einmal pro Klasse im Prototyp zu erstellen. Stattdessen erstellen Sie für jede Instanz eine Kopie der Funktion
  • Vorschlag: Fügen Sie dem abstrakten Eigenschaftszugriff eine Boilerplate hinzu und erzwingen Sie die Sichtbarkeit
    • Probleme: großer Leistungsaufwand; TypeScript wurde für große Anwendungen entwickelt
  • Vorschlag: TypeScript schließt die Konstruktor- und Prototypmethodendefinitionen bereits in einen Abschluss ein. dort private Methoden und Eigenschaften platzieren
    • Probleme beim Einfügen privater Immobilien in diesen Abschluss: Sie werden zu statischen Variablen. Es gibt keine pro Instanz
    • Probleme beim Einfügen privater Methoden in diesen Abschluss: Sie haben keinen Zugriff thisohne eine Problemumgehung
  • Vorschlag: Die privaten Variablennamen werden automatisch entstellt
    • Gegenargumente: Das ist eine Namenskonvention, kein Sprachkonstrukt. Zerfleische es selbst
  • Vorschlag: Kommentieren Sie private Methoden mit @privatesolchen Minifizierern, die erkennen, dass durch Annotation die Methodennamen effektiv minimiert werden können
    • Keine wesentlichen Gegenargumente zu diesem

Allgemeine Gegenargumente zum Hinzufügen von Sichtbarkeitsunterstützung in ausgegebenem Code:

  • Das Problem ist, dass JavaScript selbst keine Sichtbarkeitsmodifikatoren hat - dies ist nicht das Problem von TypeScript
  • In der JavaScript-Community gibt es bereits ein etabliertes Muster: Stellen Sie privaten Eigenschaften und Methoden einen Unterstrich voran, der besagt: "Fahren Sie auf eigenes Risiko fort."
  • Wenn TypeScript-Designer sagten, dass wirklich private Eigenschaften und Methoden nicht "möglich" sind, meinten sie "unter unseren Designbeschränkungen nicht möglich", insbesondere:
    • Das emittierte JS ist idiomatisch
    • Boilerplate ist minimal
    • Kein zusätzlicher Overhead im Vergleich zu normalem JS OOP
Alexanderbird
quelle
Wenn diese Antwort aus diesem Gespräch stammt: typescript.codeplex.com/discussions/397651 -, geben Sie bitte einen Link an: D
Roman M. Koss
1
Ja, das ist das Gespräch - aber ich habe auf Sean Feldmans Antwort auf diese Frage verwiesen , wo er den Link bereitstellt. Da er die Arbeit gefunden hat, um den Link zu finden, wollte ich ihm die Ehre erweisen.
Alexanderbird
2

In TypeScript sind private Funktionen nur innerhalb der Klasse verfügbar. Mögen

Geben Sie hier die Bildbeschreibung ein

Und es wird ein Fehler angezeigt, wenn Sie versuchen, auf ein privates Mitglied zuzugreifen. Hier ist das Beispiel:

Geben Sie hier die Bildbeschreibung ein

Hinweis: Mit Javascript ist es in Ordnung und beide Funktionen sind außerhalb zugänglich.

Muhammad Awais
quelle
4
OP: "Aber in reinem JavaScript ist alles da" - Ich glaube nicht, dass Sie das Problem ansprechen, dass das generierte JavaScript die "privaten" Funktionen öffentlich verfügbar macht
Alexanderbird
1
@alexanderbird Ich denke, dass er sagen wollte, dass TypeScript normalerweise genug ist. Wenn wir in TypeScript entwickeln, bleiben wir im Projektumfang dabei, sodass der Schutz vor JavaScript keine große Rolle spielt. Denn zunächst ist der ursprüngliche Code für Entwickler von Bedeutung, nicht für transpilierte (JavaScript).
Roman M. Koss
1
Wenn Sie keine JavaScript-Bibliothek schreiben und veröffentlichen, spielt der transpilierte Code eine Rolle
Alexanderbird
Ihre Antwort ist nicht zum Thema.
canbax
1

Mir ist klar, dass dies eine ältere Diskussion ist, aber es könnte immer noch nützlich sein, meine Lösung für das Problem der angeblich privaten Variablen und Methoden in einem TypeScript zu teilen, das in die öffentliche Schnittstelle der kompilierten JavaScript-Klasse "eindringt".

Für mich ist dieses Problem rein kosmetischer Natur, dh es geht nur um die visuelle Unordnung, wenn eine Instanzvariable in DevTools angezeigt wird. Mein Fix besteht darin, private Deklarationen in einer anderen Klasse zu gruppieren, die dann in der Hauptklasse instanziiert und einer private(in JS noch öffentlich sichtbaren) Variablen mit einem Namen wie __(doppelter Unterstrich) zugewiesen wird .

Beispiel:

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);

Wenn die myClassInstanz in DevTools angezeigt wird, werden nicht alle "privaten" Mitglieder mit wirklich öffentlichen Mitgliedern vermischt (was in korrekt überarbeitetem Code im wirklichen Leben sehr unübersichtlich werden kann), sondern ordentlich in der reduzierten __Eigenschaft gruppiert :

Geben Sie hier die Bildbeschreibung ein

Kaspisches Canuck
quelle
1
Ich mag das. Sieht sauber aus.
0

Hier ist ein wiederverwendbarer Ansatz zum Hinzufügen geeigneter privater Eigenschaften:

/**
 * 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);
    }
}

Angenommen, Sie haben Clientirgendwo eine Klasse , die zwei private Eigenschaften benötigt:

  • prop1: string
  • prop2: number

Im Folgenden wird beschrieben, wie Sie es implementieren:

// 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;
    }
}

Und wenn Sie nur ein einzelnes Privateigentum benötigen, wird es noch einfacher, da Sie ClientPrivatein diesem Fall keine definieren müssten .

Es ist erwähnenswert, dass die Klasse zum größten Teil Privatenur eine gut lesbare Signatur bietet, während die direkte Verwendung WeakMapdies nicht tut.

vitaly-t
quelle