Novos campos de classe #private no JavaScript
O que são, como funcionam e porquê são como são
Campos de classe private estão no Stage 2 no processo padrão de especificações do JavaScript. Ainda não foi finalizado, mas o comitê de padrões JavaScript espera que a nova feature seja desenvolvida e eventualmente, incluída como padrão (embora ainda possa mudar).
A sintaxe (atualmente) é assim:
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
}equals(point) {
return this.#x === point.#x && this.#y === point.#y;
}
}
Existem duas partes principais nessa sintaxe:
- Definindo campos privados
- Referenciando campos privados
Definindo Campos Privados
Definir campos privados é basicamente o mesmo que definir campos públicos:
class Foo {
publicFieldName = 1;
#privateFieldName = 2;
}
Para acessar um campo privado, você precisa primeiro defini-lo. Se você não quiser instanciar um valor quando estiver definindo a propriedade, você pode fazer assim:
class Foo {
#privateFieldName;
}
Referenciando Campos Privados
Referenciar campos privados funciona de forma semelhante ao acessar qualquer outra propriedade, exceto que existe uma sintaxe especial.
class Foo {
publicFieldName = 1;
#privateFieldName = 2;add() {
return this.publicFieldName + this.#privateFieldName;
}
}
Também existe uma versão shorthand de this.#
:
method() {
#privateFieldName;
}
Que seria o mesmo que:
method() {
this.#privateFieldName;
}
Referenciando campos privados de instâncias
Referenciar campos privados não está limitado somente ao this
. Você também pode acessar campos privados em valores que são instâncias de sua classe:
class Foo {
#privateValue = 42;static getPrivateValue(foo) {
return foo.#privateValue;
}
}Foo.getPrivateValue(new Foo()); // >> 42
No exemplo acima, foo
é uma instância de Foo
então podemos acessar #privateValue
dentro da definição de classe.
Métodos Privados (em breve?)
Os campos privados estão vindo como parte de uma proposal que se concentra apenas na adição de campos de classe privado. Essa proposal não faz nenhuma alteração nos métodos de classe, portanto, os métodos de classe privados estarão em uma followup proposal e muito provavelmente se parecerão com isso:
class Foo {
constructor() {
this.#method();
}#method() {
// ...
}
}
Enquanto isso não ocorre de fato, você pode continuar atribuindo funções a campos privados:
class Foo {
constructor() {
this.#method();
}
#method = () => {
// ...
};
}
Encapsulamento
Se você estiver usando uma instância de uma classe, você não pode fazer referência aos campos privados dessa classe. Você só pode fazer referência a campos privados dentro da classe que os define.
class Foo {
#bar;method() {
this.#bar; // Works
}
}let foo = new Foo();foo.#bar; // Invalid!
Além disso, para ser verdadeiramente privado, você não pode nem mesmo detectar que existe um campo privado.
Para garantir que você não consiga detectar um campo privado, precisamos permitir campos públicos com o mesmo nome.
class Foo {
bar = 1; // public bar
#bar = 2; // private bar
}
Se campos privados não permitissem campos públicos com o mesmo nome, você poderia detectar a existência de campos privados tentando escrever em uma propriedade com o mesmo nome:
foo.bar = 1; // Error: `bar` is private! (boom... detected)
Ou a versão mais “silenciosa”:
foo.bar = 1;
console.log(foo.bar); // `undefined` (boom... detected again)
Este encapsulamento também deve ser verdade para subclasses. Uma subclasse deve poder ter um campo de mesmo nome sem ter que se preocupar com a classe pai.
class Foo {
#fieldName = 1;
}class Bar extends Foo {
fieldName = 2; // Works!
}
Nota do Autor: Para mais informações sobre a motivação por trás do encapsulamento ou “hard private”, leia esta seção nas FAQ.
Então, por que usar hashtag?
Muitas pessoas estão se perguntando “por que não seguir as convenções de muitas outras linguagens e usar uma palavra-chave private
”?
Aqui está um exemplo dessa sintaxe:
class Foo {
private value;
equals(foo) {
return this.value === foo.value;
}
}
Vamos ver as duas partes da sintaxe separadamente.
Por que as declarações não usam a palavra chave private?
A palavra-chave private
é usada em muitas linguagens diferentes para declarar campos privados.
Vamos analisar a sintaxe de uma linguagem qualquer, como essa:
class EnterpriseFoo {
public bar;
private baz;method() {
this.bar;
this.baz;
}
}
Nessas linguagens, os campos públicos e privados são acessados da mesma forma. Então, faz sentido que eles sejam definidos dessa forma.
No entanto, em JavaScript, por causa de não podermos usar this.field
para propriedades privadas (o que vou chegar em um segundo), precisamos de uma maneira de comunicar sintaticamente esse relacionamento. Ao usar o #
em ambos os lugares, é muito mais claro saber o que está sendo referenciado.
Por que as referências precisam de #hashtag?
Nós precisamos usar this.#field
ao invés de this.field
alguns motivos:
Por causa de#encapsulamento (veja a seção “Encapsulamento” acima), precisamos permitir campos públicos e privados com o mesmo nome e ao mesmo tempo. Então, acessar um campo privado não pode ser apenas uma pesquisa normal.
- Campos públicos em JavaScript podem ser referenciados via
this.field
orthis['field']
. Campos privados não são capazes de suportar a segunda sintaxe (porque precisa ser estático) e isso pode acabar levando à confusão. - Você precisará de checks custosos:
Vamos dar uma olhada no código de exemplo abaixo:
class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
}equals(other) {
return this.#x === other.#x && this.#y === other.#y;
}
}
Observe como estamos referenciando outros other.#x
e other.#y
. Ao acessar os campos privados, estamos assumindo que other
é uma instanceof
da nossa classe Point
.
Como usamos a sintaxe #hashtag, informaremos ao compilador JavaScript que buscamos propriedades privadas da classe atual.
Mas o que aconteceria se não usássemos o #hashtag?
equals(otherPoint) {
return this.x === otherPoint.x && this.y === otherPoint.y;
}
Bem, agora nós temos um problema: Como sabemos o que é otherPoint
?
JavaScript não tem um sistema de static type, então otherPoint
poderia ser qualquer coisa.
Que é um problema por duas razões:
- Nossa função se comporta de forma diferente, dependendo do tipo de valor que você passa para ele: Às vezes, acessando uma propriedade privada, outras vezes procurando uma propriedade pública.
- Teríamos que verificar o tipo de
otherPoint
todas as vezes:
if (otherPoint instanceof Point && isNotSubClass(otherPoint, Point)) {
return getPrivate(otherPoint, 'foo');
} else {
return otherPoint.foo;
}
Pior ainda, teríamos que fazer isso para cada acesso de propriedade dentro de uma classe para verificar se estamos referenciando uma propriedade privada.
O acesso à propriedade já é muito lento, então definitivamente não queremos adicionar mais peso a ele.
TL;DR: Precisamos usar uma #hashtag para propriedades privadas porque a alternativa de usar acessos de propriedade padrão criaria um comportamento inesperado e resultaria em enormes problemas de desempenho.
Os campos privados são uma ótima adição ao JavaScript. Graças a todas as pessoas maravilhosas e trabalhadoras do TC39 que fizeram / estão fazendo isso acontecer!
Créditos
- JavaScript’s new #private class fields, escrito originalmente por James Kyle