TypeScript ha tre modificatori di accesso per i membri di una classe: public (il predefinito), protected e private. Controllano dove si può usare un membro, e il compilatore segnala qualsiasi accesso dal posto sbagliato.
L'ultima riga è un errore di compilazione, ma guarda cosa ha stampato eseguendola comunque: 123-45-6789. È il fatto più importante di questa pagina, e la sezione su private lo spiega.
Cosa consente ogni modificatore
| Modificatore | Dentro la classe | In una sottoclasse | Da fuori | Imposto a runtime |
|---|---|---|---|---|
public (predefinito) | sì | sì | sì | niente da imporre |
protected | sì | sì | no | no |
private | sì | no | no | no |
#name (JavaScript) | sì | no | no | sì |
readonly | lettura, e scrittura nel costruttore | lettura | lettura | no |
I modificatori funzionano su campi, metodi, getter e setter, costruttori e parameter property (constructor(private id: string)). Scrivere public è facoltativo, e molti codebase lo omettono.
private è un controllo in compilazione
TypeScript cancella private insieme al resto dei tipi. La classe compilata ha una proprietà normale, quindi tutto ciò che non passa dal type checker la vede: chiamanti in JavaScript semplice, JSON.stringify, Object.keys e perfino la notazione con parentesi quadre di TypeScript stesso, permessa di proposito come via di fuga.
Va bene per lo scopo di private: dire agli altri sviluppatori (e al tuo editor) che un membro è un dettaglio di implementazione. Non è un confine di sicurezza, e un campo private finirà nei log e nelle risposte JSON se non lo togli tu.
Campi #private: imposti a runtime
JavaScript ha i propri campi privati, scritti con #. È il motore a imporli: fuori dalla classe, obj.#field non è nemmeno sintassi valida, e il campo non compare in Object.keys, JSON.stringify o console.log.
Scrivere s.#token fuori dalla classe è l'errore TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier), e a differenza del caso private non c'è modo di aggirarlo nemmeno a runtime. Vedi i campi privati in JavaScript per le regole a runtime.
private vs #private: quale usare
private x | #x | |
|---|---|---|
| Controllato da | il compilatore | il motore JavaScript |
Visibile a Object.keys / JSON.stringify | sì | no |
Accesso con parentesi obj["x"] | consentito | non possibile |
Una sottoclasse può dichiarare il proprio x | no, va in conflitto | sì, ogni classe ha il proprio #x |
Copiato da uno spread { ...obj } | sì | no |
| Sintassi sui metodi | private helper() | #helper() |
Usa #private quando i dati devono restare privati a runtime (token, stato interno che gli utenti di una libreria non devono toccare) o quando vuoi che JSON.stringify li lasci fuori. Usa private quando l'obiettivo è solo un'API pubblica pulita, quando un framework deve leggere il campo per riflessione, o per seguire lo stile esistente di un codebase. Non combinarli: private #x è l'errore TS18010 (An accessibility modifier cannot be used with a private identifier).
protected e sottoclassi
Un membro protected è disponibile dentro la classe e in qualsiasi classe che la estende, ma non sulle istanze da fuori.
Una regola sorprende molti. Dentro Polygon puoi leggere sides su this o su un altro Polygon, ma non su un semplice Shape passato come parametro: other.sides con other: Shape è l'errore TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). Una sottoclasse può raggiungere i membri protected solo degli oggetti che appartengono al suo ramo della gerarchia.
Una sottoclasse può rendere pubblico un membro protected ridichiarandolo, ma non può rendere protected o private un membro pubblico. Ridichiaralo con un inizializzatore (public override sides = 6) o solo come tipo (declare public sides: number). Un semplice public sides: number; viene rifiutato con TS2564 e TS2612, perché con i class field moderni reimposterebbe il valore ereditato a undefined dopo il ritorno di super().
readonly
readonly impedisce la riassegnazione dopo la costruzione. Il campo si può impostare nella dichiarazione o nel costruttore; qualsiasi assegnazione successiva è l'errore TS2540.
Qui emergono due limiti. readonly è superficiale: l'array non si può sostituire, ma il suo contenuto può cambiare (tipizzalo readonly string[] per bloccare push). E come private, sparisce a runtime, quindi l'assegnazione rifiutata dal compilatore è stata eseguita lo stesso. Per un valore che non deve cambiare a runtime, usa Object.freeze o un getter senza setter. La pagina su readonly tratta Readonly<T> e gli array readonly.
readonly si combina con i modificatori di accesso: private readonly cache = new Map<string, number>() è un pattern comune per un campo interno che non viene mai riassegnato.
Errori comuni
- Trattare
privatecome sicurezza. A runtime non c'è più. Usa#fieldper i dati che devono restare nascosti, e non passare mai un oggetto con dei segreti direttamente aJSON.stringify. - Scrivere
publicovunque. È il predefinito; aggiungerlo non cambia nulla. - Usare
protectedper tutto ciò che è "interno". Se nessuna sottoclasse ne ha bisogno,privatetiene la superficie più piccola. - Aspettarsi che
readonlycongeli i dati annidati. Impedisce solo la riassegnazione della proprietà stessa.
Domande frequenti
Quali sono i modificatori di accesso in TypeScript?
public (il predefinito: accessibile ovunque), protected (dentro la classe e le sue sottoclassi) e private (solo dentro la classe). readonly è un modificatore separato che impedisce la riassegnazione dopo il costruttore e si combina con uno qualsiasi dei tre.
Che differenza c'è tra private e #private in TypeScript?
private viene controllato solo dal compilatore; il JavaScript emesso ha una proprietà normale, quindi obj["secret"], JSON.stringify e Object.keys la vedono comunque. #secret è un campo privato di JavaScript: lo impone il runtime e il codice fuori dalla classe non può leggerlo in alcun modo.
private in TypeScript è davvero privato?
Solo in compilazione. Dopo la compilazione il campo è una proprietà normale che qualsiasi codice JavaScript può leggere. TypeScript permette perfino l'accesso con le parentesi quadre (obj["field"]) ai membri privati come via di fuga. Usa #field quando la privacy deve reggere a runtime.
Che differenza c'è tra protected e private in TypeScript?
Un membro private è visibile solo dentro la classe che lo dichiara. Un membro protected è visibile anche dentro le sottoclassi. Nessuno dei due è accessibile su un'istanza da fuori della gerarchia di classi.
Le proprietà readonly si possono modificare in TypeScript?
Una proprietà readonly si può assegnare nella sua dichiarazione o nel costruttore, e da nessun'altra parte (altrimenti TS2540). È superficiale: un readonly tags: string[] non si può sostituire, ma tags.push() funziona ancora. Usa readonly string[] per bloccare anche quello.