Il problema dei trattini bassi
Per anni JavaScript non ha avuto campi privati. La convenzione era mettere un trattino basso davanti al nome di una proprietà e sperare che tutti la rispettassero:
_count sembra privato ma non lo è. Qualsiasi chiamante può leggerlo, scriverlo o cancellarlo. Il trattino basso è un cartello educato sulla porta; la porta però è spalancata.
JavaScript moderno ha risolto il problema con veri campi privati, contrassegnati da un #.
Il # lo rende privato
Metti il prefisso # davanti al nome del campo nella dichiarazione e in ogni punto in cui vi accedi:
Dall'interno della classe, this.#count funziona normalmente. Dall'esterno, non esiste:
Il # fa letteralmente parte del nome del campo. Non è una parola chiave modificatrice come private in altri linguaggi: è un simbolo che il parser usa per cercare uno slot separato e protetto sull'oggetto. Per questo l'errore compare in fase di parsing, prima ancora che il codice venga eseguito.
Metodi privati e getter
I campi non sono l'unica cosa che puoi rendere privata. Metodi, getter e setter accettano tutti il prefisso #:
#assertPositive è una funzione di supporto interna. Non fa parte dell'API pubblica, quindi renderla davvero privata significa che nessuno la chiamerà per sbaglio dall'esterno, e nessuno potrà dipenderne: così resti libero di rinominarla o eliminarla in futuro.
Membri statici privati
Anche i membri statici possono essere privati. Aggiungi il prefisso # allo stesso modo:
I membri statici privati vivono sulla classe stessa, non sulle istanze. Sono utili per contatori, cache o configurazioni che non devono uscire dalla classe.
Le sottoclassi non li vedono
Questo spiazza chi arriva da Java o C#. I campi privati di JavaScript sono privati per classe, non privati per istanza. Una sottoclasse non può accedere ai campi privati della classe padre:
In JavaScript non esiste protected. Se una sottoclasse ha bisogno dei dati, la classe padre deve esporre un metodo, un getter o (meno spesso) un campo non privato. La scelta è voluta: privato significa davvero privato, e l'ereditarietà non ci apre dei buchi.
Controllare un campo privato con in
A volte vuoi confermare che un oggetto appartenga davvero alla tua classe: si chiama brand check. L'operatore in funziona con i nomi dei campi privati dentro la classe:
Dato che solo Wallet può creare oggetti con #balance, #balance in obj è un test affidabile per sapere se obj è una vera istanza di Wallet. In alcuni casi limite è più veloce e sicuro di instanceof, perché i campi privati non si possono falsificare dall'esterno.
Una trappola comune: gli oggetti semplici non li hanno
I campi privati vivono sulle istanze create dal costruttore della classe. Se provi a usarne uno su un oggetto non creato con new, viene lanciato un errore:
Chiamare il metodo con un this che non è un Point lancia un errore a runtime. È lo stesso meccanismo che sta dietro al brand check visto sopra: i campi privati sono legati alla classe specifica che li ha dichiarati, non a qualunque oggetto che per caso abbia la forma giusta.
Quando usare #
Usa i campi privati come scelta predefinita ogni volta che uno stato o una funzione di supporto non fa parte dell'API pubblica della classe. I motivi:
- Libertà di refactoring. I chiamanti non possono dipendere da dettagli interni che letteralmente non vedono.
- Incapsulamento reale. Nessuna lettura, scrittura o cancellazione accidentale da codice esterno.
- Autocompletamento più pulito. Gli editor non suggeriscono i membri privati a chi chiama dall'esterno.
Usa proprietà pubbliche quando qualcosa fa davvero parte dell'interfaccia. Usa un getter (get name()) quando vuoi un accesso in sola lettura a un campo privato. Lascia perdere la convenzione del trattino basso: era un ripiego per una lacuna che il linguaggio ora ha colmato.
#celsius è la memoria nascosta; celsius e fahrenheit sono viste in sola lettura. Il chiamante non può corrompere lo stato interno, e la classe è libera di cambiare in futuro il modo in cui memorizza il valore.
Prossimo passo: i prototipi
Le classi sono in gran parte zucchero sintattico sopra il sistema dei prototipi di JavaScript, il modello più vecchio e più fondamentale su cui il linguaggio è davvero costruito. Capire i prototipi spiega perché this si comporta così, come funziona davvero l'ereditarietà e cosa fa extends dietro le quinte. È l'argomento della prossima pagina.
Domande frequenti
Come si dichiara un campo privato in JavaScript?
Metti il prefisso # davanti al nome del campo, sia nella dichiarazione sia ogni volta che vi accedi. class Counter { #count = 0; increment() { this.#count++; } } crea un campo davvero privato. Il # fa parte del nome, non è un operatore.
Qual è la differenza tra #field e _field in JavaScript?
#field e _field in JavaScript?_field è solo una convenzione di nomi: la proprietà resta pubblica e chiunque può leggerla o scriverla. #field è imposto dal linguaggio: il codice fuori dalla classe non può proprio accedervi, e se ci prova lancia un SyntaxError già in fase di parsing. Usa # quando vuoi una privacy reale.
Le sottoclassi possono accedere ai campi privati della classe padre?
No. I campi privati sono limitati alla classe che li dichiara: nemmeno le sottoclassi possono toccarli. Se una sottoclasse ha bisogno di accedervi, la classe padre deve esporre un metodo o un getter. È più restrittivo di protected in altri linguaggi, ed è voluto.
Posso controllare se un oggetto ha un campo privato?
Sì, con l'operatore in dentro la classe: #field in obj restituisce true se obj ha quel campo privato. È utile per i brand check, cioè per confermare che un oggetto sia davvero un'istanza della tua classe prima di chiamarci sopra dei metodi.