Quali sono le differenze tra la parola chiave privata e i campi privati ​​in TypeScript?


Risposte:


43

Parola chiave privata

La parola chiave privata in TypeScript è un'annotazione in fase di compilazione . Indica al compilatore che una proprietà dovrebbe essere accessibile solo all'interno di quella classe:

class PrivateKeywordClass {
    private value = 1;
}

const obj = new PrivateKeywordClass();
obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.

Tuttavia, il controllo del tempo di compilazione può essere facilmente ignorato, ad esempio eliminando le informazioni sul tipo:

const obj = new PrivateKeywordClass();
(obj as any).value // no compile error

Anche la privateparola chiave non viene applicata in fase di esecuzione

JavaScript emesso

Quando si compila TypeScript in JavaScript, la privateparola chiave viene semplicemente rimossa:

class PrivateKeywordClass {
    private value = 1;
}

diventa:

class PrivateKeywordClass {
    constructor() {
        this.value = 1;
    }
}

Da questo, puoi capire perché la privateparola chiave non offre alcuna protezione di runtime: nel JavaScript generato è solo una normale proprietà JavaScript.

Campi privati

I campi privati assicurano che le proprietà siano mantenute private in fase di esecuzione :

class PrivateFieldClass {
    #value = 1;

    getValue() { return this.#value; }
}

const obj = new PrivateFieldClass();

// You can't access '#value' outside of class like this
obj.value === undefined // This is not the field you are looking for.
obj.getValue() === 1 // But the class itself can access the private field!

// Meanwhile, using a private field outside a class is a runtime syntax error:
obj.#value

// While trying to access the private fields of another class is 
// a runtime type error:
class Other {
    #value;

    getValue(obj) {
        return obj.#value // TypeError: Read of private field #value from an object which did not contain the field
    }
}

new Other().getValue(new PrivateKeywordClass());

TypeScript genererà anche un errore di compilazione se si tenta di utilizzare un campo privato al di fuori di una classe:

Errore durante l'accesso a un campo privato

I campi privati ​​provengono da una proposta JavaScript e funzionano anche in JavaScript normale.

JavaScript emesso

Se utilizzi campi privati ​​in TypeScript e scegli come target versioni precedenti di JavaScript per il tuo output, come es6o es2018, TypeScript proverà a generare codice che emula il comportamento di runtime dei campi privati

class PrivateFieldClass {
    constructor() {
        _x.set(this, 1);
    }
}
_x = new WeakMap();

Se si sta effettuando il targeting esnext, TypeScript emetterà il campo privato:

class PrivateFieldClass {
    constructor() {
        this.#x = 1;
    }
    #x;
}

Quale dovrei usare?

Dipende da cosa stai cercando di ottenere.

La privateparola chiave è un ottimo valore predefinito. Realizza ciò che è stato progettato per realizzare ed è stato usato con successo dagli sviluppatori TypeScript per anni. E se disponi di una base di codice esistente, non è necessario cambiare tutto il codice per utilizzare i campi privati. Ciò è particolarmente vero se non si sta effettuando il targeting esnext, poiché il JS che TS emette per i campi privati ​​può avere un impatto sulle prestazioni. Tieni inoltre presente che i campi privati ​​presentano altre differenze sottili ma importanti rispetto alla privateparola chiave

Tuttavia, se è necessario imporre la riservatezza del runtime o si sta generando esnextJavaScript, è necessario utilizzare i campi privati.

Tieni inoltre presente che le convenzioni organizzazione / comunità sull'uso dell'uno o dell'altro si evolveranno man mano che i campi privati ​​diventano più diffusi all'interno degli ecosistemi JavaScript / TypeScript

Altre differenze di nota

  • I campi privati ​​non vengono restituiti con Object.getOwnPropertyNamesmetodi simili

  • I campi privati ​​non sono serializzati da JSON.stringify

  • Esistono casi limite di importanza intorno all'eredità.

    TypeScript, ad esempio, vieta la dichiarazione di una proprietà privata in una sottoclasse con lo stesso nome di una proprietà privata nella superclasse.

    class Base {
        private value = 1;
    }
    
    class Sub extends Base {
        private value = 2; // Compile error:
    }
    

    Questo non è vero con i campi privati:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
    
  • Una privateproprietà privata della parola chiave senza un inizializzatore non genererà una dichiarazione di proprietà nel JavaScript emesso:

    class PrivateKeywordClass {
        private value?: string;
        getValue() { return this.value; }
    }
    

    Compila per:

    class PrivateKeywordClass {
        getValue() { return this.value; }
    }
    

    Considerando che i campi privati ​​generano sempre una dichiarazione di proprietà:

    class PrivateKeywordClass {
        #value?: string;
        getValue() { return this.#value; }
    }
    

    Compila per (durante il targeting esnext):

    class PrivateKeywordClass {
        #value;
        getValue() { return this.#value; }
    }
    

Ulteriori letture:


4

Casi d'uso: #-campi privati

Prefazione:

Privacy in fase di compilazione e runtime

#-private field forniscono privacy in fase di compilazione e runtime, che non è "hackerabile". È un meccanismo per impedire l'accesso a un membro esterno al corpo della classe in qualsiasi modo diretto .

class A {
    #a: number;
    constructor(a: number) {
        this.#a = a;
    }
}

let foo: A = new A(42);
foo.#a; // error, not allowed outside class bodies
(foo as any).#bar; // still nope.

Eredità di classe sicura

#i campi privati ​​hanno un ambito unico. Le gerarchie di classi possono essere implementate senza sovrascritture accidentali di proprietà private con nomi uguali.

class A { 
    #a = "a";
    fnA() { return this.#a; }
}

class B extends A {
    #a = "b"; 
    fnB() { return this.#a; }
}

const b = new B();
b.fnA(); // returns "a" ; unique property #a in A is still retained
b.fnB(); // returns "b"

Il compilatore TS genera fortunatamente un errore quando le privateproprietà rischiano di essere sovrascritte (vedi questo esempio ). Ma a causa della natura di una funzione di compilazione, tutto è ancora possibile in fase di esecuzione, dato che gli errori di compilazione vengono ignorati e / o utilizzati codice JS.

Biblioteche esterne

Gli autori delle biblioteche possono eseguire il refactoring di #identificatori privati ​​senza causare una rottura sostanziale per i client. Gli utenti della biblioteca dall'altra parte sono protetti dall'accesso ai campi interni.

L'API JS omette i #campi privati

Le funzioni e i metodi JS integrati ignorano i #campi privati. Ciò può comportare una selezione delle proprietà più prevedibile in fase di esecuzione. Esempi: Object.keys, Object.entries, JSON.stringify, for..inloop e altri ( esempio di codice ; vedi anche di Matt Bierner risposta ):

class Foo {
    #bar = 42;
    baz = "huhu";
}

Object.keys(new Foo()); // [ "baz" ]

Casi d'uso: privateparola chiave

Prefazione:

Accesso all'API e allo stato della classe interna (privacy solo in fase di compilazione)

privatei membri di una classe sono proprietà convenzionali in fase di esecuzione. Possiamo usare questa flessibilità per accedere all'API interna della classe o allo stato dall'esterno. Al fine di soddisfare i controlli del compilatore, meccanismi come asserzioni di tipo, accesso dinamico alle proprietà o @ts-ignorepossono essere utilizzati tra gli altri.

Esempio con asserzione di tipo ( as/ <>) e anyassegnazione di variabile digitata:

class A { 
    constructor(private a: number) { }
}

const a = new A(10);
a.a; // TS compile error
(a as any).a; // works
const casted: any = a; casted.a // works

TS consente persino l'accesso dinamico alle proprietà di un privatemembro con un tratteggio di escape :

class C {
  private foo = 10;
}

const res = new C()["foo"]; // 10, res has type number

Dove può avere senso l'accesso privato? (1) test unitari, (2) situazioni di debug / registrazione o (3) altri scenari di casi avanzati con classi interne al progetto (elenco aperto).

L'accesso alle variabili interne è un po 'contraddittorio, altrimenti non le avresti fatte privatein primo luogo. Per fare un esempio, si suppone che i test unitari siano scatole nere / grigie con campi privati ​​nascosti come dettagli di implementazione. In pratica, tuttavia, potrebbero esserci approcci validi da caso a caso.

Disponibile in tutti gli ambienti ES

I privatemodificatori TS possono essere utilizzati con tutti i target ES. #i campi privati ​​sono disponibili solo per target ES2015/ ES6o successivi. In ES6 +, WeakMapviene utilizzato internamente come implementazione di livello inferiore (vedere qui ). I #campi privati nativi attualmente richiedono target esnext.

Coerenza e compatibilità

I team potrebbero utilizzare le linee guida per la codifica e le regole di linter per imporre l'utilizzo privatecome unico modificatore di accesso. Questa restrizione può aiutare con coerenza ed evitare confusione con la #notazione di campo privata in modo compatibile con le versioni precedenti.

Se necessario, le proprietà dei parametri (abbreviazione di assegnazione del costruttore) sono un blocco dello spettacolo. Possono essere usati solo con privateparole chiave e non ci sono ancora piani per implementarli per i #campi privati.

Altri motivi

  • privatepotrebbe fornire prestazioni di runtime migliori in alcuni casi di livellamento (vedere qui ).
  • Fino ad ora non ci sono metodi di classe privata disponibili in TS.
  • Ad alcune persone piace la privatenotazione della parola chiave 😊.

Nota su entrambi

Entrambi gli approcci creano un tipo di tipo nominale o di marca al momento della compilazione.

class A1 { private a = 0; }
class A2 { private a = 42; }

const a: A1 = new A2(); 
// error: "separate declarations of a private property 'a'"
// same with hard private fields

Inoltre, entrambi consentono l'accesso tra istanze: un'istanza di classe Apuò accedere a membri privati ​​di altre Aistanze:

class A {
    private a = 0;
    method(arg: A) {
        console.log(arg.a); // works
    }
}

fonti

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.