Menu

Private Fields em JavaScript: a sintaxe # nas classes

Entenda como o prefixo # torna campos e métodos de classe realmente privados em JavaScript — a sintaxe, as regras e por que a convenção do underscore não resolve.

O problema do underscore

Durante anos, o JavaScript não teve campos privados de verdade. A convenção era colocar um underscore na frente da propriedade e torcer para que todo mundo respeitasse:

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

_count parece privado, mas não é. Qualquer parte do código pode ler, escrever ou até deletar esse valor. O underscore é só uma plaquinha educada na porta — a porta em si continua escancarada.

O JavaScript moderno resolveu isso com campos privados de verdade, marcados com #.

A sintaxe # torna o campo privado

Basta colocar # antes do nome do campo, tanto na declaração quanto em cada lugar onde ele for acessado:

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

De dentro da classe, this.#count funciona normalmente. De fora, ele simplesmente não existe:

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

O # faz parte do nome do campo, literalmente. Não é uma palavra-chave modificadora como o private de outras linguagens — é um símbolo (sigil) que o parser usa para acessar um slot separado e protegido no objeto. É por isso que o erro aparece em tempo de parsing, antes mesmo do código rodar.

Métodos privados e getters com

Campos não são as únicas coisas que você pode marcar como privadas. Métodos, getters e setters também aceitam o prefixo #:

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

#assertPositive é um helper interno. Ele não faz parte da API pública, então torná-lo realmente privado garante que ninguém vai chamá-lo por acidente de fora — e ninguém consegue depender dele, o que te deixa livre para renomear ou remover depois.

Membros estáticos privados

Membros estáticos também podem ser privados. Basta prefixar com # do mesmo jeito:

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

Estáticos privados ficam na própria classe, não nas instâncias. Úteis para contadores, caches ou configurações que não devem vazar para fora da classe.

Subclasses não enxergam esses campos

Esse detalhe pega muita gente que vem de Java ou C#. Em JavaScript, os campos privados são privados da classe, e não privados da instância. Ou seja, uma subclasse não consegue acessar os campos privados da classe pai:

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

Em JavaScript não existe protected. Se uma subclasse precisa acessar os dados, a classe pai tem que expor isso através de um método, getter ou (mais raramente) um campo não privado. Essa decisão é proposital: privado é privado mesmo, e a herança não abre brechas nisso.

Verificando um campo privado com in

Em alguns casos, você quer confirmar se um objeto realmente pertence à sua classe — o famoso brand check. O operador in funciona com nomes de campos privados dentro da classe:

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

Como só Wallet consegue criar objetos com #balance, testar #balance in obj é uma forma confiável de verificar se obj é realmente uma instância de Wallet. Em alguns casos, é até mais rápido e seguro do que usar instanceof, já que campos privados não podem ser forjados de fora da classe.

Pegadinha comum: objetos literais não têm campos privados

Os campos privados existem apenas nas instâncias criadas pelo construtor da classe. Se você tentar usar um campo privado em um objeto que não foi criado com new, a linguagem lança um erro:

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

Chamar o método com um this que não seja uma instância de Point lança um erro em tempo de execução. Esse é o mecanismo por trás da verificação de marca mostrada antes: os campos privados ficam amarrados à classe específica que os declarou, e não a qualquer objeto que simplesmente tenha a mesma cara.

Quando usar # em campos privados

Use campos privados por padrão sempre que algum estado ou auxiliar não fizer parte da API pública da classe. Os motivos:

  • Liberdade para refatorar. Quem consome a classe não consegue depender de detalhes internos que sequer enxerga.
  • Encapsulamento de verdade. Nada de leituras, escritas ou exclusões acidentais vindas de fora.
  • Autocomplete mais limpo. Os editores não sugerem membros privados para quem está usando a classe por fora.

Use propriedades públicas quando algo realmente faz parte da interface. Use um getter (get name()) quando quiser acesso somente leitura a um campo privado. E pode esquecer a convenção do underline: ela era uma gambiarra para suprir uma lacuna que a linguagem agora já preencheu.

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

#celsius é o armazenamento interno, enquanto celsius e fahrenheit são apenas views de leitura. Quem consome a classe não tem como corromper o estado interno, e a própria classe fica livre para mudar a forma como guarda esse valor no futuro.

A seguir: Prototypes

As classes são, em boa parte, açúcar sintático em cima do sistema de prototypes do JavaScript — o modelo mais antigo e fundamental sobre o qual a linguagem foi de fato construída. Entender prototypes deixa claro por que o this se comporta do jeito que se comporta, como a herança funciona de verdade e o que acontece quando uma classe faz extends por baixo dos panos. É o assunto da próxima página.

Perguntas frequentes

Como declarar um campo privado em JavaScript?

Basta colocar # na frente do nome do campo — tanto na declaração quanto em todo acesso. Por exemplo: class Counter { #count = 0; increment() { this.#count++; } } cria um campo de fato privado. Vale lembrar: o # faz parte do nome, não é um operador.

Qual a diferença entre #field e _field em JavaScript?

_field é só convenção de nomenclatura — a propriedade continua pública e qualquer um pode ler ou escrever nela. Já #field é garantido pela própria linguagem: código fora da classe simplesmente não consegue acessar, e tentar gera um SyntaxError já em tempo de parse. Se você quer privacidade de verdade, use #.

Subclasses conseguem acessar campos privados da classe pai?

Não. Private fields têm escopo limitado à classe que os declara — nem mesmo subclasses enxergam. Se a subclasse precisar do valor, a classe pai precisa expor um método ou getter. É uma regra mais rígida que o protected de outras linguagens, e isso é proposital.

Dá para verificar se um objeto tem um determinado campo privado?

Sim, usando o operador in dentro da classe: #field in obj retorna true se obj tiver aquele campo privado. É bastante útil para brand checks — ou seja, confirmar que o objeto é realmente uma instância da sua classe antes de chamar métodos nele.

Aprenda a programar com o Coddy

COMEÇAR