Quais são as diferenças entre a palavra-chave privada e os campos privados no TypeScript?

Respostas:

43

Palavra-chave privada

A palavra-chave privada no TypeScript é uma anotação em tempo de compilação . Diz ao compilador que uma propriedade só deve ser acessível dentro dessa classe:

class PrivateKeywordClass {
    private value = 1;
}

const obj = new PrivateKeywordClass();
obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.

No entanto, a verificação do tempo de compilação pode ser facilmente ignorada, por exemplo, descartando as informações de tipo:

const obj = new PrivateKeywordClass();
(obj as any).value // no compile error

A privatepalavra-chave também não é aplicada no tempo de execução

JavaScript emitido

Ao compilar o TypeScript para JavaScript, a privatepalavra-chave é simplesmente removida:

class PrivateKeywordClass {
    private value = 1;
}

Torna-se:

class PrivateKeywordClass {
    constructor() {
        this.value = 1;
    }
}

A partir disso, você pode ver por que a privatepalavra - chave não oferece proteção de tempo de execução: no JavaScript gerado, é apenas uma propriedade JavaScript normal.

Campos privados

Os campos privados garantem que as propriedades sejam mantidas privadas em tempo de execução :

class PrivateFieldClass {
    #value = 1;

    getValue() { return this.#value; }
}

const obj = new PrivateFieldClass();

// You can't access '#value' outside of class like this
obj.value === undefined // This is not the field you are looking for.
obj.getValue() === 1 // But the class itself can access the private field!

// Meanwhile, using a private field outside a class is a runtime syntax error:
obj.#value

// While trying to access the private fields of another class is 
// a runtime type error:
class Other {
    #value;

    getValue(obj) {
        return obj.#value // TypeError: Read of private field #value from an object which did not contain the field
    }
}

new Other().getValue(new PrivateKeywordClass());

O TypeScript também emitirá um erro de tempo de compilação se você tentar usar um campo privado fora de uma classe:

Erro ao acessar um campo privado

Os campos particulares são provenientes de uma proposta JavaScript e também funcionam em JavaScript normal.

JavaScript emitido

Se você usa campos particulares no TypeScript e está direcionando versões mais antigas do JavaScript para sua saída, como es6ou es2018, o TypeScript tentará gerar código que emule o comportamento do tempo de execução dos campos privados

class PrivateFieldClass {
    constructor() {
        _x.set(this, 1);
    }
}
_x = new WeakMap();

Se você estiver direcionando esnext, o TypeScript emitirá o campo privado:

class PrivateFieldClass {
    constructor() {
        this.#x = 1;
    }
    #x;
}

Qual devo usar?

Depende do que você está tentando alcançar.

A privatepalavra-chave é um ótimo padrão. Ele realiza o que foi projetado para realizar e tem sido usado com sucesso pelos desenvolvedores do TypeScript há anos. E se você possui uma base de código existente, não precisa alternar todo o seu código para usar campos particulares. Isso é especialmente verdadeiro se você não estiver direcionando esnext, pois o JS que o TS emite para campos particulares pode ter um impacto no desempenho. Lembre-se também de que os campos particulares têm outras diferenças sutis, mas importantes, doprivate palavra chave

No entanto, se você precisar impor a privacidade do tempo de execução ou estiver produzindo esnextJavaScript, deverá usar campos privados.

Lembre-se também de que as convenções da organização / comunidade sobre o uso de um ou de outro também evoluirão à medida que os campos privados se tornarem mais difundidos nos ecossistemas JavaScript / TypeScript

Outras diferenças de nota

  • Campos privados não são retornados por Object.getOwnPropertyNamesmétodos semelhantes

  • Os campos particulares não são serializados por JSON.stringify

  • Existem casos importantes em torno da herança.

    O TypeScript, por exemplo, proíbe declarar uma propriedade privada em uma subclasse com o mesmo nome que uma propriedade privada na superclasse.

    class Base {
        private value = 1;
    }
    
    class Sub extends Base {
        private value = 2; // Compile error:
    }

    Isso não é verdade com campos particulares:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
  • Uma privatepropriedade privada de palavra-chave sem um inicializador não gerará uma declaração de propriedade no JavaScript emitido:

    class PrivateKeywordClass {
        private value?: string;
        getValue() { return this.value; }
    }

    Compila para:

    class PrivateKeywordClass {
        getValue() { return this.value; }
    }

    Enquanto os campos privados sempre geram uma declaração de propriedade:

    class PrivateKeywordClass {
        #value?: string;
        getValue() { return this.#value; }
    }

    Compila para (quando segmentar esnext):

    class PrivateKeywordClass {
        #value;
        getValue() { return this.#value; }
    }

Leitura adicional:

Matt Bierner
fonte
4

Casos de uso: # -private fields

Prefácio:

Tempo de compilação e privacidade de tempo de execução

#campos -Privadas fornecer tempo de compilação e privacidade de tempo de execução, o que não é "hackable". É um mecanismo para impedir o acesso a um membro de fora do corpo da classe de maneira direta .

class A {
    #a: number;
    constructor(a: number) {
        this.#a = a;
    }
}

let foo: A = new A(42);
foo.#a; // error, not allowed outside class bodies
(foo as any).#bar; // still nope.

Herança de classe segura

#Os campos privados recebem um escopo exclusivo. As hierarquias de classe podem ser implementadas sem substituições acidentais de propriedades particulares com nomes iguais.

class A { 
    #a = "a";
    fnA() { return this.#a; }
}

class B extends A {
    #a = "b"; 
    fnB() { return this.#a; }
}

const b = new B();
b.fnA(); // returns "a" ; unique property #a in A is still retained
b.fnB(); // returns "b"

Felizmente, o compilador TS emite um erro quando as privatepropriedades correm o risco de serem substituídas (consulte este exemplo ). Porém, devido à natureza de um recurso de tempo de compilação, tudo ainda é possível no tempo de execução, dado que os erros de compilação são ignorados e / ou é emitido o código JS utilizado.

Bibliotecas externas

Os autores da biblioteca podem refatorar #identificadores privados sem causar uma alteração de interrupção para os clientes. Os usuários da biblioteca do outro lado estão protegidos contra o acesso a campos internos.

A API JS omite os #campos -private

As funções e métodos JS internos ignoram os #campos -private. Isso pode resultar em uma seleção de propriedade mais previsível em tempo de execução. Exemplos: Object.keys, Object.entries, JSON.stringify, for..inloop e outros ( exemplo de código ; ver também de Matt Bierner resposta ):

class Foo {
    #bar = 42;
    baz = "huhu";
}

Object.keys(new Foo()); // [ "baz" ]

Casos de uso: privatekeyword

Prefácio:

Acesso à API e ao estado da classe interna (privacidade apenas em tempo de compilação)

privatemembros de uma classe são propriedades convencionais em tempo de execução. Podemos usar essa flexibilidade para acessar a API ou o estado interno da classe de fora. Para satisfazer as verificações do compilador, mecanismos como asserções de tipo, acesso dinâmico à propriedade ou @ts-ignorepodem ser usados ​​entre outros.

Exemplo com asserção de tipo ( as/ <>) e anyatribuição de variável digitada:

class A { 
    constructor(private a: number) { }
}

const a = new A(10);
a.a; // TS compile error
(a as any).a; // works
const casted: any = a; casted.a // works

O TS ainda permite o acesso dinâmico à propriedade de um privatemembro com uma saída de escape :

class C {
  private foo = 10;
}

const res = new C()["foo"]; // 10, res has type number

Onde o acesso privado pode fazer sentido? (1) testes de unidade, (2) situações de depuração / registro em log ou (3) outros cenários de casos avançados com classes internas do projeto (lista aberta).

O acesso a variáveis ​​internas é um pouco contraditório - caso contrário, você não as teria criado privateem primeiro lugar. Para dar um exemplo, os testes de unidade devem ser caixas pretas / cinza com campos particulares ocultos como detalhes da implementação. Na prática, porém, pode haver abordagens válidas de caso para caso.

Disponível em todos os ambientes ES

privateModificadores de TS podem ser usados ​​com todos os destinos de ES. #Os campos privados estão disponíveis apenas para target ES2015/ ES6ou superiores. No ES6 +, WeakMapé usado internamente como implementação de nível inferior (veja aqui ). #Atualmente, os campos nativos particulares requerem target esnext.

Consistência e compatibilidade

As equipes podem usar diretrizes de codificação e regras de linter para impor o uso privatecomo o único modificador de acesso. Essa restrição pode ajudar na consistência e evitar confusão com a #notação de campo -private de uma maneira compatível com versões anteriores.

Se necessário, as propriedades dos parâmetros (abreviação de atribuição do construtor) são um limitador de exibição. Eles podem ser usados ​​apenas com a privatepalavra - chave e ainda não há planos para implementá-los em #campos privados.

Outras razões

  • privatepode fornecer melhor desempenho em tempo de execução em alguns casos de nível inferior (veja aqui ).
  • Até o momento, não há métodos rígidos de classe privada disponíveis no TS.
  • Algumas pessoas gostam mais da privatenotação de palavras - chave 😊.

Nota sobre ambos

Ambas as abordagens criam algum tipo de tipo nominal ou de marca no momento da compilação.

class A1 { private a = 0; }
class A2 { private a = 42; }

const a: A1 = new A2(); 
// error: "separate declarations of a private property 'a'"
// same with hard private fields

Além disso, ambos permitem acesso entre instâncias: uma instância da classe Apode acessar membros particulares de outras Ainstâncias:

class A {
    private a = 0;
    method(arg: A) {
        console.log(arg.a); // works
    }
}

Fontes

ford04
fonte