Perché posso accedere ai membri privati ​​di TypeScript quando non dovrei essere in grado di farlo?


108

Sto esaminando l'implementazione di membri privati ​​in TypeScript e lo trovo un po 'confuso. Intellisense non consente l'accesso a membri privati, ma in JavaScript puro, è tutto lì. Questo mi fa pensare che TS non implementa correttamente i membri privati. qualche idea?

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

Ti stai chiedendo perché IntelliSense non ti dà il membro privato sulla riga con l'avviso ()?
Esrange

7
No. Mi chiedo perché TS abbia un privato quando questo è solo uno zucchero per intellisense e non proprio per il JavaScript in cui compila. Questo codice eseguito in typescriptlang.org/Playground avvisa il valore del membro privato.
Sean Feldman

Come accennato, devi dichiarare gli elementi come una variabile in un contesto privato per averli privati. Immagino che il dattiloscritto non lo faccia perché può essere inefficiente rispetto all'aggiunta al prototipo. Si confonde anche con la definizione del tipo (i membri privati ​​non fanno veramente parte della classe)
Shane

Se vuoi delle variabili private reali che esistono sul prototipo, ci vuole un po 'di overhead, ma ho scritto una libreria chiamata ClassJS che fa proprio questo su GitHub: github.com/KthProg/ClassJS .
KthProg

Risposte:


97

Proprio come con il controllo del tipo, la privacy dei membri viene applicata solo all'interno del compilatore.

Una proprietà privata viene implementata come una proprietà normale e il codice esterno alla classe non è autorizzato ad accedervi.

Per rendere qualcosa di veramente privato all'interno della classe, non può essere un membro della classe, sarebbe una variabile locale creata all'interno di uno scope di funzione all'interno del codice che crea l'oggetto. Ciò significherebbe che non puoi accedervi come un membro della classe, cioè utilizzando la thisparola chiave.


25
Non è insolito per un programmatore javascript inserire una variabile locale in un costruttore di oggetti e utilizzarla come campo privato. Sono sorpreso che non abbiano supportato qualcosa del genere.
Eric,

2
@Eric: poiché TypeScript utilizza il prototipo per i metodi invece di aggiungere metodi come prototipi all'interno del costruttore, una variabile locale nel costruttore non è raggiungibile dai metodi. Potrebbe essere possibile creare una variabile locale all'interno del wrapper della funzione per la classe, ma non ho ancora trovato un modo per farlo. Tuttavia, quella sarebbe ancora una variabile locale e non un membro privato.
Guffa

40
Questo è qualcosa su cui ho fornito feedback. Credo che dovrebbe offrire la possibilità di creare un modello di modulo rivelatore, in modo che i membri privati ​​possano rimanere privati ​​e quelli pubblici possano essere accessibili in JavaScript. Questo è un modello comune e fornirebbe la stessa accessibilità in TS e JS.
John Papa

C'è una soluzione che puoi usare per i membri statici privati: basarat.com/2013/03/real-private-static-class-members-in.html
basarat

1
@BasaratAli: Questa è una variabile statica disponibile all'interno dei metodi della classe, ma non è un membro della classe, ovvero non vi si accede utilizzando la thisparola chiave.
Guffa

37

JavaScript supporta le variabili private.

function MyClass() {
    var myPrivateVar = 3;

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

In TypeScript questo sarebbe espresso in questo modo:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

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

MODIFICARE

Questo approccio dovrebbe essere usato RISPARMIANDO dove è assolutamente necessario. Ad esempio, se è necessario memorizzare temporaneamente una password nella cache.

L'uso di questo pattern comporta dei costi di prestazione (irrilevante per Javascript o Typescript) e dovrebbe essere utilizzato solo dove assolutamente necessario.


Il dattiloscritto non lo fa TUTTO il tempo impostando var _thisper l'uso in funzioni con ambito? Perché avresti scrupoli a farlo nell'ambito della classe?
DrSammyD

No. var _questo è solo un riferimento a questo.
Martin

2
Chiamarle più accuratamente variabili del costruttore, non private. Quelli non sono visibili nei metodi prototipo.
Roman M. Koss

1
oh sì, scusa il problema era invece altro, il fatto che per ogni istanza che crei, doSomething verrà creato di nuovo, perché non fa parte della catena del prototipo.
Barbu Barbu

1
@BarbuBarbu Sì, sono d'accordo. Questo è un grosso problema con questo approccio e uno dei motivi per cui dovrebbe essere evitato.
Martin

11

Una volta che il supporto per WeakMap è più ampiamente disponibile, c'è una tecnica interessante descritta in dettaglio nell'esempio # 3 qui .

Consente dati privati ​​ED evita i costi di prestazione dell'esempio di Jason Evans consentendo ai dati di essere accessibili da metodi prototipo invece che solo metodi di istanza.

La pagina MDN WeakMap collegata elenca il supporto del browser su Chrome 36, Firefox 6.0, IE 11, Opera 23 e 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)();
    }
  }
}

Mi è piaciuto! Fondamentalmente significa nascondere le proprietà private in una classe aggregata. Il più divertente sarà ... Che ne dici di aggiungere il supporto per i protectedparametri? : D
Roman M. Koss

2
@RamtinSoltani L'articolo collegato riporta che a causa di come funzionano le mappe deboli, questo non impedirà la raccolta dei rifiuti. Se qualcuno volesse essere più sicuro durante l'utilizzo di questa tecnica, potrebbe implementare il proprio codice di eliminazione che elimina la chiave dell'istanza della classe da ciascuna delle mappe deboli.
Ryan Thomas

1
Dalla pagina MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Al contrario, le WeakMap native contengono riferimenti "deboli" agli oggetti chiave, il che significa che non impediscono la garbage collection nel caso in cui non ci siano altri riferimenti all'oggetto chiave. Ciò evita anche di impedire la raccolta di dati obsoleti dei valori nella mappa.
Ryan Thomas

@ RyanThomas Vero, quello era un vecchio commento che ho lasciato qualche tempo fa. Le WeakMaps, al contrario di Maps, non causeranno perdite di memoria. Quindi è sicuro usare questa tecnica.
Ramtin Soltani

@RamtinSoltani Quindi cancelli il tuo vecchio commento?>
ErikE

10

Dal momento che TypeScript 3.8 sarà rilasciato, sarai in grado di dichiarare un campo privato a cui non è possibile accedere o addirittura rilevato al di fuori della classe contenente .

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.

I campi privati ​​iniziano con il #carattere

Tieni presente che questi campi privati ​​saranno qualcosa di diverso dai campi contrassegnati con privateparola chiave

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


4

Grazie a Sean Feldman per il collegamento alla discussione ufficiale su questo problema - vedere la sua risposta per il collegamento.

Ho letto la discussione a cui si è collegato ed ecco un riassunto dei punti chiave:

  • Suggerimento: proprietà private nel costruttore
    • problemi: impossibile accedere dalle funzioni del prototipo
  • Suggerimento: metodi privati ​​nel costruttore
    • problemi: come con le proprietà, in più si perde il vantaggio in termini di prestazioni di creare una funzione una volta per classe nel prototipo; invece crei una copia della funzione per ogni istanza
  • Suggerimento: aggiungi boilerplate per astrarre l'accesso alla proprietà e rafforzare la visibilità
    • problemi: maggiore sovraccarico delle prestazioni; TypeScript è progettato per applicazioni di grandi dimensioni
  • Suggerimento: TypeScript racchiude già le definizioni del metodo del costruttore e del prototipo in una chiusura; mettere metodi e proprietà privati ​​lì
    • problemi con l'inserimento di proprietà private in quella chiusura: diventano variabili statiche; non ce n'è uno per istanza
    • problemi con l'inserimento di metodi privati ​​in quella chiusura: non hanno accesso thissenza una sorta di soluzione alternativa
  • Suggerimento: manipola automaticamente i nomi delle variabili private
    • controargomenti: questa è una convenzione di denominazione, non un costrutto di linguaggio. Mangialo da solo
  • Suggerimento:@private annota metodi privati ​​con minificatori così che riconoscono che l'annotazione può minimizzare efficacemente i nomi dei metodi
    • Nessun argomento controverso significativo a questo

Controargomentazioni complessive per l'aggiunta del supporto di visibilità nel codice emesso:

  • il problema è che JavaScript stesso non ha modificatori di visibilità - questo non è un problema di TypeScript
  • esiste già un modello stabilito nella comunità JavaScript: anteporre alle proprietà e ai metodi privati ​​un carattere di sottolineatura, che dice "procedi a tuo rischio"
  • quando i progettisti di TypeScript hanno affermato che proprietà e metodi veramente privati ​​non sono "possibili", intendevano "non possibili sotto i nostri vincoli di progettazione", in particolare:
    • Il JS emesso è idiomatico
    • Boilerplate è minimo
    • Nessun overhead aggiuntivo rispetto al normale JS OOP

Se questa risposta proveniva da questa conversazione: typescript.codeplex.com/discussions/397651 -, fornire un collegamento: D
Roman M. Koss

1
Sì, questa è la conversazione, ma ho collegato alla risposta di Sean Feldman a questa domanda , dove fornisce il collegamento. Dato che ha fatto il lavoro per trovare il collegamento, ho voluto dargli il merito.
alexanderbird

2

In TypeScript le funzioni private sono accessibili solo all'interno della classe. Piace

inserisci qui la descrizione dell'immagine

E mostrerà un errore quando proverai ad accedere a un membro privato. Ecco l'esempio:

inserisci qui la descrizione dell'immagine

Nota: andrà bene con javascript ed entrambe le funzioni sono accessibili dall'esterno.


4
OP: "ma in JavaScript puro, è tutto lì" - Non penso che affronti il ​​problema che il JavaScript generato espone pubblicamente le funzioni "private"
alexanderbird

1
@alexanderbird Penso che volesse dire che TypeScript è abbastanza di solito. Quando sviluppiamo in TypeScript, rimaniamo nell'ambito del progetto, quindi la privacy da JavaScript non è un grosso problema. Perché prima di tutto, il codice originale è importante per lo sviluppatore, non per quelli trasferiti (JavaScript).
Roman M. Koss

1
A meno che tu non stia scrivendo e pubblicando una libreria JavaScript, il codice transpilato ha importanza
alexanderbird

la tua risposta è fuori tema.
canbax

1

Mi rendo conto che questa è una discussione precedente, ma potrebbe essere ancora utile condividere la mia soluzione al problema delle variabili e dei metodi presumibilmente privati ​​in un TypeScript che "trapela" nell'interfaccia pubblica della classe JavaScript compilata.

Per me questo problema è puramente estetico, cioè riguarda tutto il disordine visivo quando una variabile di istanza viene visualizzata in DevTools. La mia soluzione è raggruppare le dichiarazioni private insieme all'interno di un'altra classe che viene quindi istanziata nella classe principale e assegnata a una privatevariabile (ma ancora visibile pubblicamente in JS) con un nome come __(doppio trattino basso).

Esempio:

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

Quando l' myClassistanza viene visualizzata in DevTools, invece di vedere tutti i suoi membri "privati" mescolati con quelli veramente pubblici (che possono diventare molto visivamente disordinati in codice reale correttamente refactored) li vedi raggruppati ordinatamente all'interno della __proprietà compressa :

inserisci qui la descrizione dell'immagine


1
Mi piace. Sembra pulito.

0

Ecco un approccio riutilizzabile per aggiungere proprietà private appropriate:

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

Supponiamo che tu abbia una classe Clientda qualche parte che necessita di due proprietà private:

  • prop1: string
  • prop2: number

Di seguito è riportato come implementarlo:

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

E se tutto ciò di cui hai bisogno è una singola proprietà privata, diventa ancora più semplice, perché non avresti bisogno di definirne alcuna ClientPrivate in quel caso.

Vale la pena notare che, per la maggior parte, la classe Privateoffre solo una firma ben leggibile, mentre l'uso diretto di WeakMapnon lo fa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.