Menu
Français

Champs privés JavaScript : la syntaxe # dans les classes

Comment le préfixe # rend les champs et méthodes d'une classe réellement privés en JavaScript — syntaxe, règles, et pourquoi la convention du underscore ne suffit plus.

Le souci avec les underscores

Pendant des années, JavaScript n'a pas eu de vrais champs privés. La convention, c'était de préfixer une propriété avec un underscore et d'espérer que tout le monde joue le jeu :

index.js
Output
Click Run to see the output here.

_count a beau ressembler à un champ privé, il n'en est rien. N'importe quel appelant peut le lire, le modifier ou le supprimer. Le tiret bas, c'est un simple panneau « privé » sur la porte — mais la porte, elle, reste grande ouverte.

JavaScript moderne a corrigé le tir avec de vrais champs privés, signalés par un #.

Le # rend le champ vraiment privé

Il suffit de préfixer le nom du champ avec #, à la fois dans la déclaration et à chaque accès :

index.js
Output
Click Run to see the output here.

Depuis l'intérieur de la classe, this.#count fonctionne normalement. De l'extérieur, il n'existe tout simplement pas :

index.js
Output
Click Run to see the output here.

Le # fait littéralement partie du nom du champ. Ce n'est pas un mot-clé modificateur comme private dans d'autres langages — c'est un sigle que le parseur utilise pour retrouver un emplacement distinct et protégé sur l'objet. Voilà pourquoi l'erreur apparaît dès le parsing, avant même que le code ne s'exécute.

Méthodes privées et getters

Les champs ne sont pas les seuls à pouvoir être marqués comme privés. Les méthodes, getters et setters acceptent tous le préfixe # :

index.js
Output
Click Run to see the output here.

#assertPositive est un utilitaire interne. Comme il ne fait pas partie de l'API publique, le rendre réellement privé garantit que personne ne l'appellera par mégarde depuis l'extérieur — et personne ne pourra s'en rendre dépendant, ce qui te laisse libre de le renommer ou le supprimer plus tard.

Membres statiques privés

Les membres statiques peuvent eux aussi être privés. Il suffit de les préfixer avec #, comme d'habitude :

index.js
Output
Click Run to see the output here.

Les membres privés statiques existent sur la classe elle-même, pas sur ses instances. Pratique pour des compteurs, des caches ou de la configuration qui ne doivent pas fuiter hors de la classe.

Les sous-classes n'y ont pas accès

Voilà qui surprend souvent les développeurs qui viennent de Java ou C#. En JavaScript, les champs privés sont privés à la classe, pas privés à l'instance. Une sous-classe ne peut pas aller piocher dans les champs privés de sa classe parente :

index.js
Output
Click Run to see the output here.

Il n'existe pas de protected en JavaScript. Si une sous-classe a besoin d'accéder aux données, la classe parente doit exposer une méthode, un getter ou, plus rarement, un champ non privé. Ce choix de conception est assumé : privé veut vraiment dire privé, et l'héritage ne vient pas percer de trous dans cette encapsulation.

Vérifier la présence d'un champ privé avec in

Il arrive qu'on veuille s'assurer qu'un objet appartient bien à notre classe — c'est ce qu'on appelle un brand check. L'opérateur in fonctionne avec les noms de champs privés, à condition d'être utilisé à l'intérieur de la classe :

index.js
Output
Click Run to see the output here.

Comme seul Wallet peut créer des objets avec #balance, #balance in obj est un test fiable pour vérifier que obj est bien une vraie instance de Wallet. C'est plus rapide et plus sûr que instanceof dans certains cas limites, puisqu'un champ privé ne peut pas être falsifié depuis l'extérieur.

Piège classique : les objets littéraux n'en ont pas

Les champs privés n'existent que sur les instances créées par le constructeur de la classe. Si tu tentes d'en utiliser un sur un objet qui n'a pas été créé via new, ça lève une erreur :

index.js
Output
Click Run to see the output here.

Appeler la méthode avec un this qui n'est pas un Point déclenche une erreur à l'exécution. C'est exactement le mécanisme derrière le brand check montré plus haut : les champs privés sont liés à la classe précise qui les a déclarés, pas à n'importe quel objet qui « ressemble » à la bonne forme.

Quand utiliser # en JavaScript

Par défaut, utilisez des champs privés dès qu'un état interne ou un helper ne fait pas partie de l'API publique de la classe. Les raisons sont simples :

  • Liberté de refactoring. Impossible pour le code appelant de dépendre de détails internes qu'il ne peut littéralement pas voir.
  • Véritable encapsulation. Plus de lectures, d'écritures ou de suppressions accidentelles depuis l'extérieur.
  • Autocomplétion plus propre. Les éditeurs ne proposent pas les membres privés aux appelants externes.

Gardez les propriétés publiques pour ce qui fait vraiment partie de l'interface. Utilisez un getter (get name()) quand vous voulez exposer un champ privé en lecture seule. Oubliez la convention du tiret bas : c'était un contournement pour combler un manque que le langage a désormais réglé.

index.js
Output
Click Run to see the output here.

#celsius est le stockage caché, tandis que celsius et fahrenheit sont des vues en lecture seule. L'appelant ne peut pas corrompre l'état interne, et la classe reste libre de changer plus tard sa façon de stocker la valeur.

La suite : les prototypes

Les classes ne sont au fond qu'un sucre syntaxique posé par-dessus le système de prototypes de JavaScript — un modèle plus ancien et plus fondamental sur lequel repose vraiment le langage. Comprendre les prototypes permet d'expliquer pourquoi this se comporte comme il le fait, comment fonctionne réellement l'héritage et ce qu'une classe extends en coulisses. C'est justement le sujet de la page suivante.

Questions fréquentes

Comment déclarer un champ privé en JavaScript ?

Il faut préfixer le nom du champ avec #, aussi bien à la déclaration qu'à chaque accès. Par exemple : class Counter { #count = 0; increment() { this.#count++; } }. Le # fait partie intégrante du nom, ce n'est pas un opérateur.

Quelle est la différence entre #field et _field en JavaScript ?

_field n'est qu'une convention de nommage — la propriété reste publique et n'importe qui peut la lire ou la modifier. #field, en revanche, est imposé par le langage lui-même : le code extérieur à la classe ne peut tout simplement pas y accéder, et toute tentative déclenche une SyntaxError dès le parsing. Utilise # quand tu veux une vraie confidentialité.

Les classes filles peuvent-elles accéder aux champs privés de la classe parente ?

Non. Les champs privés sont limités à la classe qui les déclare — même une sous-classe n'y a pas accès. Si une classe fille en a besoin, la classe parente doit exposer une méthode ou un getter. C'est volontairement plus strict que le protected d'autres langages.

Peut-on vérifier qu'un objet possède un champ privé donné ?

Oui, avec l'opérateur in utilisé à l'intérieur de la classe : #field in obj renvoie true si obj possède bien ce champ privé. Pratique pour faire un brand check, c'est-à-dire confirmer qu'un objet est réellement une instance de ta classe avant d'appeler ses méthodes.

Apprendre à coder avec Coddy

COMMENCER