Por que posso acessar membros privados do TypeScript quando não deveria?

108

Estou olhando para a implementação de membros privados no TypeScript e acho um pouco confuso. O Intellisense não permite acessar um membro privado, mas em JavaScript puro, está tudo lá. Isso me faz pensar que o TS não implementa membros privados corretamente. Alguma ideia?

class Test{
  private member: any = "private member";
}
alert(new Test().member);
Sean Feldman
fonte
Você está se perguntando por que o IntelliSense não fornece o membro privado na linha com o alert ()?
esrange
7
Não. Estou me perguntando por que o TS tem um privado quando isso é apenas um açúcar para o intellisense, e não realmente para o JavaScript para o qual é compilado. Este código executado em typescriptlang.org/Playground alerta o valor do membro privado.
Sean Feldman,
Conforme mencionado, você deve declarar itens como uma variável em um contexto privado para mantê-los privados. Estou supondo que o texto digitado não faz isso porque pode ser ineficiente em comparação com o acréscimo ao protótipo. Também mexe com a definição do tipo (os membros privados não fazem realmente parte da classe)
Shane
Se você quiser variáveis ​​privadas reais que existam no protótipo, isso exige alguma sobrecarga, mas eu escrevi uma biblioteca chamada ClassJS que faz exatamente isso no GitHub: github.com/KthProg/ClassJS .
KthProg de

Respostas:

97

Assim como com a verificação de tipo, a privacidade dos membros só é imposta no compilador.

Uma propriedade privada é implementada como uma propriedade regular e o código fora da classe não tem permissão para acessá-la.

Para tornar algo realmente privado dentro da classe, não pode ser um membro da classe, seria uma variável local criada dentro de um escopo de função dentro do código que cria o objeto. Isso significaria que você não pode acessá-lo como um membro da classe, ou seja, usando a thispalavra - chave.

Guffa
fonte
25
Não é incomum para um programador de javascript colocar uma variável local em um construtor de objeto e usá-la como um campo privado. Estou surpreso que eles não apoiaram algo assim.
Eric,
2
@Eric: Como o TypeScript usa o protótipo para métodos em vez de adicionar métodos como protótipos dentro do construtor, uma variável local no construtor não pode ser alcançada a partir dos métodos. Pode ser possível criar uma variável local dentro do wrapper de função para a classe, mas ainda não encontrei uma maneira de fazer isso. No entanto, isso ainda seria uma variável local e não um membro privado.
Guffa
40
Isso é algo sobre o qual tenho fornecido feedback. Acredito que deva oferecer a opção de criar um Padrão de Módulo Revelador, para que os membros privados possam permanecer privados e os públicos possam ser acessíveis em JavaScript. Este é um padrão comum e forneceria a mesma acessibilidade em TS e JS.
John Papa
Há uma solução que você pode usar para membros estáticos privados: basarat.com/2013/03/real-private-static-class-members-in.html
basarat
1
@BasaratAli: Essa é uma variável estática que está disponível dentro dos métodos da classe, mas não é membro da classe, ou seja, você não acessa usando a thispalavra - chave.
Guffa
37

JavaScript oferece suporte a variáveis ​​privadas.

function MyClass() {
    var myPrivateVar = 3;

    this.doSomething = function() {
        return myPrivateVar++;        
    }
}

No TypeScript, isso seria expresso assim:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

        this.doSomething = function () {
            return myPrivateVar++;
        }
    }
}

EDITAR

Essa abordagem só deve ser usada PARCIALMENTE onde for absolutamente necessária. Por exemplo, se você precisar armazenar uma senha temporariamente.

Existem custos de desempenho para usar este padrão (irrelevante de Javascript ou Typescript) e só deve ser usado quando for absolutamente necessário.

Martin
fonte
O typescript não faz isso o tempo todo definindo var _thispara uso em funções com escopo? Por que você teria escrúpulos em fazer isso no âmbito da classe?
DrSammyD
Não. Var _this é apenas uma referência a isso.
Martin
2
Mais precisamente, para chamá-los de variáveis ​​de construtor, não privadas. Esses não são visíveis nos métodos de protótipo.
Roman M. Koss
1
ah sim, desculpe, mas o problema era outro, o fato de que para cada instância que você cria, doSomething será criado novamente, porque não faz parte da cadeia de protótipos.
Barbu Barbu
1
@BarbuBarbu Sim, concordo. Este é um GRANDE problema com esta abordagem e uma das razões pelas quais deve ser evitado.
Martin
11

Uma vez que o suporte para WeakMap esteja mais amplamente disponível, há uma técnica interessante detalhada no exemplo # 3 aqui .

Ele permite dados privados E evita os custos de desempenho do exemplo de Jason Evans, permitindo que os dados sejam acessíveis a partir de métodos de protótipo em vez de apenas métodos de instância.

A página MDN WeakMap vinculada lista o suporte do navegador no Chrome 36, Firefox 6.0, IE 11, Opera 23 e Safari 7.1.

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  decrement() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}
Ryan Thomas
fonte
Eu gostei! Basicamente, significa ocultar propriedades privadas em uma classe agregada. O mais divertido será ... Que tal adicionar suporte para protectedparâmetros? : D
Roman M. Koss
2
@RamtinSoltani O artigo vinculado mostra que, devido ao funcionamento dos mapas fracos, isso não impedirá a coleta de lixo. Se alguém quisesse ser mais seguro ao usar essa técnica, poderia implementar seu próprio código de descarte que exclui a chave de instância de classe de cada um dos mapas fracos.
Ryan Thomas
1
Na página MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Por outro lado, os WeakMaps nativos mantêm referências "fracas" para objetos-chave, o que significa que eles não evitam a coleta de lixo no caso de não haver nenhuma outra referência ao objeto-chave. Isso também evita evitar a coleta de lixo de valores no mapa.
Ryan Thomas
@RyanThomas Verdade, esse foi um comentário antigo que deixei há um tempo. Os WeakMaps, ao contrário do Maps, não causarão vazamentos de memória. Portanto, é seguro usar essa técnica.
Ramtin Soltani
@RamtinSoltani Então deleta seu comentário antigo?>
ErikE
10

Como o TypeScript 3.8 será lançado, você poderá declarar um campo privado que não pode ser acessado ou mesmo detectado fora da classe que o contém .

class Person {
    #name: string

    constructor(name: string) {
        this.#name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.#name}!`);
    }
}

let jeremy = new Person("Jeremy Bearimy");

jeremy.#name
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

Os campos privados começam com #caracteres

Observe que esses campos privados serão diferentes dos campos marcados com a privatepalavra - chave

Ref. https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/

Przemek Struciński
fonte
4

Agradecimentos a Sean Feldman pelo link para a discussão oficial sobre este assunto - veja sua resposta no link.

Li a discussão à qual ele vinculou e aqui está um resumo dos pontos-chave:

  • Sugestão: propriedades privadas no construtor
    • problemas: não consigo acessar a partir de funções de protótipo
  • Sugestão: métodos privados no construtor
    • problemas: o mesmo que com as propriedades, mais você perde o benefício de desempenho de criar uma função uma vez por classe no protótipo; em vez disso, você cria uma cópia da função para cada instância
  • Sugestão: adicione clichê para abstrair o acesso à propriedade e reforçar a visibilidade
    • problemas: sobrecarga de desempenho importante; TypeScript é projetado para grandes aplicativos
  • Sugestão: o TypeScript já envolve as definições do construtor e do método do protótipo em um encerramento; coloque métodos privados e propriedades lá
    • problemas em colocar propriedades privadas nesse fechamento: eles se tornam variáveis ​​estáticas; não há um por instância
    • problemas em colocar métodos privados nesse encerramento: eles não têm acesso thissem algum tipo de solução alternativa
  • Sugestão: mutilar automaticamente os nomes das variáveis ​​privadas
    • contra-argumentos: isso é uma convenção de nomenclatura, não uma construção de linguagem. Destrua você mesmo
  • Sugestão: anote métodos privados com @privateminificadores so que reconheçam que a anotação pode reduzir efetivamente os nomes dos métodos
    • Sem contra-argumentos significativos para este

Contra-argumentos gerais para adicionar suporte de visibilidade no código emitido:

  • o problema é que o próprio JavaScript não tem modificadores de visibilidade - este não é o problema do TypeScript
  • já existe um padrão estabelecido na comunidade JavaScript: prefixe propriedades e métodos privados com um sublinhado, que diz "proceda por sua própria conta e risco"
  • quando os designers do TypeScript disseram que as propriedades e métodos verdadeiramente privados não são "possíveis", eles queriam dizer "não é possível sob nossas restrições de design", especificamente:
    • O JS emitido é idiomático
    • Boilerplate é mínimo
    • Sem sobrecarga adicional em comparação com JS OOP normal
alexanderbird
fonte
Se a resposta foi desta conversa: typescript.codeplex.com/discussions/397651 -, forneça um link: D
Roman M. Koss
1
Sim, essa é a conversa - mas vinculei a resposta de Sean Feldman a esta pergunta , onde ele fornece o link. Como ele fez o trabalho de encontrar o link, gostaria de dar o crédito a ele.
alexanderbird
2

No TypeScript, as funções privadas são acessíveis apenas dentro da classe. Gostar

insira a descrição da imagem aqui

E mostrará um erro quando você tentar acessar um membro privado. Aqui está o exemplo:

insira a descrição da imagem aqui

Nota: Vai funcionar bem com javascript e ambas as funções são acessíveis externamente.

Muhammad Awais
fonte
4
OP: "mas em JavaScript puro, está tudo lá" - não acho que você resolva o problema de que o JavaScript gerado expõe as funções "privadas" publicamente
alexanderbird
1
@alexanderbird Acho que ele queria dizer que o TypeScript normalmente é suficiente. Quando estamos desenvolvendo em TypeScript, permanecemos com ele no escopo do projeto, portanto, a privacidade do JavaScript não é um grande problema. Porque, em primeiro lugar, o código original é importante para o desenvolvedor, não os transpilados (JavaScript).
Roman M. Koss
1
A menos que você esteja escrevendo e publicando uma biblioteca JavaScript, o código transpilado importa
alexanderbird
sua resposta está fora do tópico.
canbax
1

Sei que esta é uma discussão mais antiga, mas ainda pode ser útil compartilhar minha solução para o problema das variáveis ​​e métodos supostamente privados em um TypeScript "vazando" para a interface pública da classe JavaScript compilada.

Para mim, esse problema é puramente cosmético, ou seja, é tudo sobre a desordem visual quando uma variável de instância é exibida no DevTools. Minha correção é agrupar declarações privadas dentro de outra classe que é então instanciada na classe principal e atribuída a uma privatevariável (mas ainda visível publicamente em JS) com um nome como __(sublinhado duplo).

Exemplo:

class Privates {
    readonly DEFAULT_MULTIPLIER = 2;
    foo: number;
    bar: number;

    someMethod = (multiplier: number = this.DEFAULT_MULTIPLIER) => {
        return multiplier * (this.foo + this.bar);
    }

    private _class: MyClass;

    constructor(_class: MyClass) {
        this._class = _class;
    }
}

export class MyClass {
    private __: Privates = new Privates(this);

    constructor(foo: number, bar: number, baz: number) {
        // assign private property values...
        this.__.foo = foo;
        this.__.bar = bar;

        // assign public property values...
        this.baz = baz;
    }

    baz: number;

    print = () => {
        console.log(`foo=${this.__.foo}, bar=${this.__.bar}`);
        console.log(`someMethod returns ${this.__.someMethod()}`);
    }
}

let myClass = new MyClass(1, 2, 3);

Quando a myClassinstância é visualizada no DevTools, em vez de ver todos os seus membros "privados" misturados com os realmente públicos (o que pode ficar muito confuso visualmente em código da vida real devidamente refatorado), você os vê agrupados ordenadamente dentro da __propriedade recolhida :

insira a descrição da imagem aqui

Caspian Canuck
fonte
1
Eu gosto disso. Parece limpo.
0

Esta é uma abordagem reutilizável para adicionar propriedades privadas adequadas:

/**
 * Implements proper private properties.
 */
export class Private<K extends object, V> {

    private propMap = new WeakMap<K, V>();

    get(obj: K): V {
        return this.propMap.get(obj)!;
    }

    set(obj: K, val: V) {
        this.propMap.set(obj, val);
    }
}

Digamos que você tenha uma classe em Clientalgum lugar que precise de duas propriedades privadas:

  • prop1: string
  • prop2: number

Abaixo está como você o implementa:

// our private properties:
interface ClientPrivate {
    prop1: string;
    prop2: number;
}

// private properties for all Client instances:
const pp = new Private<Client, ClientPrivate>();

class Client {
    constructor() {
        pp.set(this, {
            prop1: 'hello',
            prop2: 123
        });
    }

    someMethod() {
        const privateProps = pp.get(this);

        const prop1 = privateProps.prop1;
        const prop2 = privateProps.prop2;
    }
}

E se você só precisa de uma única propriedade privada, fica ainda mais simples, porque você não precisaria definir nenhuma ClientPrivatenesse caso.

Vale a pena notar que, na maioria das vezes, a classe Privateoferece apenas uma assinatura bem legível, enquanto o uso direto de WeakMapnão.

vitaly-t
fonte