new - Propriedades privadas em classes do JavaScript ES6




public function javascript class (20)

Uma abordagem diferente para "privado"

Em vez de lutar contra o fato de que a visibilidade privada está atualmente indisponível no ES6, decidi adotar uma abordagem mais prática que funciona bem se o seu IDE suportar JSDoc (por exemplo, Webstorm). A ideia é usar a tag @private . No que diz respeito ao desenvolvimento, o IDE impedirá que você acesse qualquer membro particular de fora de sua classe. Funciona muito bem para mim e tem sido muito útil para ocultar métodos internos, portanto, o recurso de preenchimento automático mostra exatamente o que a classe realmente queria expor. Aqui está um exemplo:

É possível criar propriedades privadas em classes ES6?

Aqui está um exemplo. Como posso impedir o acesso a instance.property ?

class Something {
  constructor(){
    this.property = "test";
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"

A única maneira de obter a verdadeira privacidade no JS é através do escopo, portanto, não há como ter uma propriedade que seja membro this que estará acessível apenas dentro do componente. A melhor maneira de armazenar dados verdadeiramente privados no ES6 é com um WeakMap.

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    privateProp1.set(this, "I am Private1");
    privateProp2.set(this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(privateProp1.get(this), privateProp2.get(this))
    };        
  }

  printPrivate() {
    console.log(privateProp1.get(this));
  }
}

Obviamente, isso é provavelmente lento, e definitivamente feio, mas proporciona privacidade.

Tenha em mente que nem isso é perfeito, porque o Javascript é tão dinâmico. Alguém ainda pode fazer

var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
    // Store 'this', 'key', and 'value'
    return oldSet.call(this, key, value);
};

para capturar valores à medida que são armazenados, portanto, se você quiser ser mais cuidadoso, precisará capturar uma referência local para .set e .get para usar explicitamente, em vez de confiar no protótipo substituível.

const {set: WMSet, get: WMGet} = WeakMap.prototype;

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    WMSet.call(privateProp1, this, "I am Private1");
    WMSet.call(privateProp2, this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
    };        
  }

  printPrivate() {
    console.log(WMGet.call(privateProp1, this));
  }
}

Atualização: uma proposta com uma sintaxe mais agradável está a caminho. Contribuições são bem vindas.

Sim, existe - para acesso com escopo em objetos - o ES6 apresenta o Symbol s .

Os símbolos são únicos, você não pode obter acesso a um do lado de fora, exceto com reflexão (como privates em Java / C #), mas qualquer pessoa que tenha acesso a um símbolo no interior pode usá-lo para acesso às chaves:

var property = Symbol();
class Something {
    constructor(){
        this[property] = "test";
    }
}

var instance = new Something();

console.log(instance.property); //=> undefined, can only access with access to the Symbol

Campos privados estão sendo implementados no padrão ECMA . Você pode começar a usá-los hoje com o pré- ajuste do babel 7 e do estágio 3. Veja o exemplo de babel REPL .

class Something {
  #property;

  constructor(){
    this.#property = "test";
  }
}

const instance = new Something();
console.log(instance.property); //=> undefined


Eu acredito que é possível obter o melhor dos dois mundos usando fechamentos dentro de construtores. Existem duas variações:

Todos os membros de dados são privados

function myFunc() {
   console.log('Value of x: ' + this.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   console.log('Enhanced value of x: ' + (this.x + 1));
}

class Test {
   constructor() {

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(internal);
      
      this.myFunc = myFunc.bind(internal);
   }
};

Alguns membros são privados

NOTA: Isto é reconhecidamente feio. Se você conhece uma solução melhor, edite essa resposta.

function myFunc(priv, pub) {
   pub.y = 3; // The Test object now gets a member 'y' with value 3.
   console.log('Value of x: ' + priv.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   pub.z = 5; // The Test object now gets a member 'z' with value 3.
   console.log('Enhanced value of x: ' + (priv.x + 1));
}

class Test {
   constructor() {
      
      let self = this;

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(null, internal, self);
      
      this.myFunc = myFunc.bind(null, internal, self);
   }
};


Para expandir a resposta do @ loganfsmyth:

Os únicos dados verdadeiramente privados em JavaScript ainda são variáveis ​​com escopo definido. Você não pode ter propriedades privadas no sentido de propriedades acessadas internamente da mesma maneira que propriedades públicas, mas pode usar variáveis ​​com escopo definido para armazenar dados privados.

Variáveis ​​com escopo

A abordagem aqui é usar o escopo da função construtora, que é privada, para armazenar dados privados. Para os métodos de acesso a esses dados privados, eles também devem ser criados no construtor, o que significa que você os recria a cada instância. Esta é uma penalidade de desempenho e memória, mas alguns acreditam que a penalidade é aceitável. A penalidade pode ser evitada para métodos que não precisam acessar dados privados, adicionando-os ao protótipo como de costume.

Exemplo:

function Person(name) {
  let age = 20; // this is private
  this.name = name; // this is public

  this.greet = function () {
    // here we can access both name and age
    console.log(`name: ${this.name}, age: ${age}`);
  };
}

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

WeakMap com escopo

Um WeakMap pode ser usado para evitar o desempenho e penalidade de memória da abordagem anterior. WeakMaps associa dados com objetos (aqui, instâncias) de tal forma que só pode ser acessado usando esse WeakMap. Portanto, usamos o método de variáveis ​​com escopo para criar um WeakMap privado e, em seguida, usamos esse WeakMap para recuperar dados privados associados a this . Isso é mais rápido que o método de variáveis ​​com escopo definido, porque todas as suas instâncias podem compartilhar um único WeakMap, portanto, você não precisa recriar métodos apenas para fazê-los acessar seus próprios WeakMaps.

Exemplo:

let Person = (function () {
  let privateProps = new WeakMap();

  class Person {
    constructor(name) {
      this.name = name; // this is public
      privateProps.set(this, {age: 20}); // this is private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
    }
  }

  return Person;
})();

let joe = new Person('Joe');
joe.greet();

// here we can access joe's name but not age

Este exemplo usa um objeto para usar um WeakMap para várias propriedades privadas; você também pode usar vários WeakMaps e usá-los como age.set(this, 20) , ou escrever um pequeno wrapper e usá-lo de outra maneira, como privateProps.set(this, 'age', 0) .

A privacidade dessa abordagem poderia, teoricamente, ser violada ao adulterar o objeto WeakMap global. Dito isto, todo o JavaScript pode ser quebrado por globals desconfigurados. Nosso código já está construído no pressuposto de que isso não está acontecendo.

(Esse método também pode ser feito com o Map , mas o WeakMap é melhor porque o Map criará vazamentos de memória, a menos que você seja muito cuidadoso e, para isso, os dois não sejam diferentes.)

Meia resposta: Símbolos com escopo

Um símbolo é um tipo de valor primitivo que pode servir como um nome de propriedade. Você pode usar o método de variável com escopo para criar um símbolo privado e, em seguida, armazenar dados privados this[mySymbol] .

A privacidade desse método pode ser violada usando Object.getOwnPropertySymbols , mas é um tanto estranho de se fazer.

Exemplo:

let Person = (function () {
  let ageKey = Symbol();

  class Person {
    constructor(name) {
      this.name = name; // this is public
      this[ageKey] = 20; // this is intended to be private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${this[ageKey]}`);
    }
  }

  return Person;
})();

let joe = new Person('Joe');
joe.greet();

// Here we can access joe's name and, with a little effort, age. ageKey is
// not in scope, but we can obtain it by listing all Symbol properties on
// joe with `Object.getOwnPropertySymbols(joe)`.

Meia resposta: sublinhados

O padrão antigo, basta usar uma propriedade pública com um prefixo de sublinhado. Embora não seja de forma alguma uma propriedade privada, essa convenção prevalece o suficiente para fazer um bom trabalho comunicando que os leitores devem tratar a propriedade como privada, o que geralmente faz o trabalho. Em troca desse lapso, obtemos uma abordagem que é mais fácil de ler, mais fácil de digitar e mais rápida.

Exemplo:

class Person {
  constructor(name) {
    this.name = name; // this is public
    this._age = 20; // this is intended to be private
  }

  greet() {
    // Here we can access both name and age
    console.log(`name: ${this.name}, age: ${this._age}`);
  }
}

let joe = new Person('Joe');
joe.greet();

// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.

Conclusão

A partir do ES2017, ainda não há uma maneira perfeita de fazer propriedades privadas. Várias abordagens têm prós e contras. Variáveis ​​com escopo são verdadeiramente privadas; Os WeakMaps com escopo definido são muito particulares e mais práticos do que variáveis ​​com escopo definido; escopo Os símbolos são razoavelmente privados e razoavelmente práticos; sublinhados são geralmente privados e muito práticos.


Para referência futura de outros observadores, estou ouvindo agora que a recomendação é usar o WeakMaps para armazenar dados privados.

Aqui está um exemplo mais claro e funcional:

function storePrivateProperties(a, b, c, d) {
  let privateData = new WeakMap;
  // unique object as key, weak map can only accept object as key, when key is no longer referened, garbage collector claims the key-value 
  let keyA = {}, keyB = {}, keyC = {}, keyD = {};

  privateData.set(keyA, a);
  privateData.set(keyB, b);
  privateData.set(keyC, c);
  privateData.set(keyD, d);

  return {
    logPrivateKey(key) {
      switch(key) {
      case "a":
        console.log(privateData.get(keyA));
        break;
      case "b":
        console.log(privateData.get(keyB));
        break;
      case "c":
        console.log(privateData.get(keyC));
        break;
      case "d":
        console.log(privateData.set(keyD));
        break;
      default:
        console.log(`There is no value for ${key}`)
      }
    }
  }
}

Usando módulos ES6 (inicialmente proposto por @ d13) funciona bem para mim. Ele não imita propriedades particulares perfeitamente, mas pelo menos você pode ter certeza de que as propriedades que devem ser privadas não vazarão para fora da sua turma. Aqui está um exemplo:

algo.js

let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

export default class Something {
  constructor(message) {
    _message = message;
  }

  say() {
    console.log(_message);
    _greet('Bob');
  }
};

Então o código consumidor pode ser assim:

import Something from './something.js';

const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception

Atualizar (Importante):

Como @DanyalAytekin descrito nos comentários, essas propriedades privadas são estáticas, portanto, globais no escopo. Eles funcionarão bem ao trabalhar com singletons, mas deve-se ter cuidado com objetos transientes. Estendendo o exemplo acima:

import Something from './something.js';
import Something2 from './something.js';

const a = new Something('a');
a.say(); // a

const b = new Something('b');
b.say(); // b

const c = new Something2('c');
c.say(); // c

a.say(); // c
b.say(); // c
c.say(); // c

Sim - você pode criar uma propriedade encapsulada , mas isso não foi feito com modificadores de acesso (public | private) pelo menos não com o ES6.

Aqui está um exemplo simples de como isso pode ser feito com o ES6:

1 Criar classe usando palavra de class

2 Dentro dele está o construtor declarar variável com escopo de bloco usando let OU const reservado palavras -> uma vez que elas são escopo de bloco elas não podem ser acessadas de fora (encapsuladas)

3 Para permitir algum controle de acesso (setters | getters) para essas variáveis, você pode declarar o método de instância dentro de seu construtor usando: this.methodName=function(){} sintaxe

"use strict";
    class Something{
        constructor(){
            //private property
            let property="test";
            //private final (immutable) property
            const property2="test2";
            //public getter
            this.getProperty2=function(){
                return property2;
            }
            //public getter
            this.getProperty=function(){
                return property;
            }
            //public setter
            this.setProperty=function(prop){
                property=prop;
            }
        }
    }

Agora vamos verificar:

var s=new Something();
    console.log(typeof s.property);//undefined 
    s.setProperty("another");//set to encapsulated `property`
    console.log(s.getProperty());//get encapsulated `property` value
    console.log(s.getProperty2());//get encapsulated immutable `property2` value

Eu uso esse padrão e sempre funcionou para mim

class Test {
    constructor(data) {
        class Public {
            constructor(prv) {

                // public function (must be in constructor on order to access "prv" variable)
                connectToDb(ip) {
                    prv._db(ip, prv._err);
                } 
            }

            // public function w/o access to "prv" variable
            log() {
                console.log("I'm logging");
            }
        }

        // private variables
        this._data = data;
        this._err = function(ip) {
            console.log("could not connect to "+ip);
        }
    }

    // private function
    _db(ip, err) {
        if(!!ip) {
		    console.log("connected to "+ip+", sending data '"+this.data+"'");
			return true;
		}
        else err(ip);
    }
}



var test = new Test(10),
		ip = "185.167.210.49";
test.connectToDb(ip); // true
test.log(); // I'm logging
test._err(ip); // undefined
test._db(ip, function() { console.log("You have got hacked!"); }); // undefined


Outra maneira semelhante aos dois últimos postados

class Example {
  constructor(foo) {

    // privates
    const self = this;
    this.foo = foo;

    // public interface
    return self.public;
  }

  public = {
    // empty data
    nodata: { data: [] },
    // noop
    noop: () => {},
  }

  // everything else private
  bar = 10
}

const test = new Example('FOO');
console.log(test.foo); // undefined
console.log(test.noop); // { data: [] }
console.log(test.bar); // undefined

Aqui, a variável myThing é privada e faz parte do fechamento:

class Person {
  constructor() {

    var myThing = "Hello World";

    return {
      thing: myThing,
      sayThing: this.sayThing
    }
  }

  sayThing() {
    console.log(this.thing);
  }
}

var person = new Person();

console.log(person);

Chegando muito tarde a esta festa, mas eu acertei a pergunta do OP em uma pesquisa, então ... Sim, você pode ter propriedades privadas envolvendo a declaração de classe em um fechamento

Há um exemplo de como eu tenho métodos privados neste código . No snippet abaixo, a classe Subscribable tem duas funções 'privadas' processe processCallbacks. Quaisquer propriedades podem ser adicionadas dessa maneira e elas são mantidas em sigilo através do uso do fechamento. A privacidade da OMI é uma necessidade rara se as preocupações forem bem separadas e o Javascript não precisar ficar inchado adicionando mais sintaxe quando um encerramento fizer o trabalho.

const Subscribable = (function(){

  const process = (self, eventName, args) => {
    self.processing.set(eventName, setTimeout(() => processCallbacks(self, eventName, args)))};

  const processCallbacks = (self, eventName, args) => {
    if (self.callingBack.get(eventName).length > 0){
      const [nextCallback, ...callingBack] = self.callingBack.get(eventName);
      self.callingBack.set(eventName, callingBack);
      process(self, eventName, args);
      nextCallback(...args)}
    else {
      delete self.processing.delete(eventName)}};

  return class {
    constructor(){
      this.callingBack = new Map();
      this.processing = new Map();
      this.toCallbacks = new Map()}

    subscribe(eventName, callback){
      const callbacks = this.unsubscribe(eventName, callback);
      this.toCallbacks.set(eventName,  [...callbacks, callback]);
      return () => this.unsubscribe(eventName, callback)}  // callable to unsubscribe for convenience

    unsubscribe(eventName, callback){
      let callbacks = this.toCallbacks.get(eventName) || [];
      callbacks = callbacks.filter(subscribedCallback => subscribedCallback !== callback);
      if (callbacks.length > 0) {
        this.toCallbacks.set(eventName, callbacks)}
      else {
        this.toCallbacks.delete(eventName)}
      return callbacks}

    emit(eventName, ...args){
      this.callingBack.set(eventName, this.toCallbacks.get(eventName) || []);
      if (!this.processing.has(eventName)){
        process(this, eventName, args)}}}})();

Eu gosto dessa abordagem porque separa as preocupações bem e mantém as coisas realmente privadas. A única desvantagem é a necessidade de usar 'self' (ou algo similar) para se referir a 'this' no conteúdo privado.


Eu encontrei uma solução muito simples, basta usar Object.freeze(). Claro que o problema é que você não pode adicionar nada ao objeto mais tarde.

class Cat {
    constructor(name ,age) {
        this.name = name
        this.age = age
        Object.freeze(this)
    }
}

let cat = new Cat('Garfield', 5)
cat.age = 6 // doesn't work, even throws an error in strict mode

Eu me deparei com este post ao procurar a melhor prática para "dados privados para classes". Foi mencionado que alguns dos padrões teriam problemas de desempenho.

Eu montei alguns testes jsperf baseados nos 4 principais padrões do livro online "Exploring ES6":

http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes

Os testes podem ser encontrados aqui:

https://jsperf.com/private-data-for-classes

No Chrome 63.0.3239 / Mac OS X 10.11.6, os padrões de melhor desempenho eram "Dados privados por meio de ambientes de construtor" e "Dados privados por meio de uma convenção de nomenclatura". Para mim, o Safari funcionou bem para o WeakMap, mas o Chrome não foi tão bom.

Eu não sei o impacto da memória, mas o padrão para "ambientes de construtor", que alguns tinham avisado seria um problema de desempenho foi muito eficaz.

Os 4 padrões básicos são:

Dados privados por meio de ambientes construtores

class Countdown {
    constructor(counter, action) {
        Object.assign(this, {
            dec() {
                if (counter < 1) return;
                counter--;
                if (counter === 0) {
                    action();
                }
            }
        });
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Dados privados através de ambientes de construtor 2

class Countdown {
    constructor(counter, action) {
        this.dec = function dec() {
            if (counter < 1) return;
            counter--;
            if (counter === 0) {
                action();
            }
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Dados privados por meio de uma convenção de nomenclatura

class Countdown {
    constructor(counter, action) {
        this._counter = counter;
        this._action = action;
    }
    dec() {
        if (this._counter < 1) return;
        this._counter--;
        if (this._counter === 0) {
            this._action();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Dados privados via WeakMaps

const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
    constructor(counter, action) {
        _counter.set(this, counter);
        _action.set(this, action);
    }
    dec() {
        let counter = _counter.get(this);
        if (counter < 1) return;
        counter--;
        _counter.set(this, counter);
        if (counter === 0) {
            _action.get(this)();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Dados privados por meio de símbolos

const _counter = Symbol('counter');
const _action = Symbol('action');

class Countdown {
    constructor(counter, action) {
        this[_counter] = counter;
        this[_action] = action;
    }
    dec() {
        if (this[_counter] < 1) return;
        this[_counter]--;
        if (this[_counter] === 0) {
            this[_action]();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Na verdade, é possível.
1. Primeiro, crie a classe e, no construtor, retorne a _publicfunção chamada .
2. Na _publicfunção chamada passar a thisreferência (para obter o acesso a todos os métodos privados e adereços) , e todos os argumentos de constructor (que serão passados new Names())
3. No _publicescopo da função, há também a Namesclasse com o acesso a this(_this referência da Namesclasse privada

class Names {
  constructor() {
    this.privateProperty = 'John';
    return _public(this, arguments);
  }
  privateMethod() { }
}

const names = new Names(1,2,3);
console.log(names.somePublicMethod); //[Function]
console.log(names.publicProperty); //'Jasmine'
console.log(names.privateMethod); //undefined
console.log(names.privateProperty); //undefind

function _public(_this, _arguments) {
  class Names {
    constructor() {
      this.publicProperty = 'Jasmine';
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

    somePublicMethod() {
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

  }
  return new Names(..._arguments);
}

Pessoalmente, eu gosto da proposta do operador bind :: e depois a combino com a solução @ d13 mencionada, mas por enquanto atenha-se à resposta do @ d13 onde você usa a exportpalavra - chave para sua classe e coloca as funções privadas no módulo.

Há uma solução mais difícil que não foi mencionada aqui que segue é uma abordagem mais funcional e permitiria ter todos os adereços / métodos privados dentro da classe.

Private.js

export const get = state => key => state[key];
export const set = state => (key,value) => { state[key] = value; }

Test.js

import { get, set } from './utils/Private'
export default class Test {
  constructor(initialState = {}) {
    const _set = this.set = set(initialState);
    const _get = this.get = get(initialState);

    this.set('privateMethod', () => _get('propValue'));
  }

  showProp() {
    return this.get('privateMethod')();
  }
}

let one = new Test({ propValue: 5});
let two = new Test({ propValue: 8});
two.showProp(); // 8
one.showProp(); // 5

comentários sobre isso seria apreciado.


Veja esta resposta para uma solução de 'classe' limpa e simples com uma interface privada e pública e suporte para composição


Você pode tentar este https://www.npmjs.com/package/private-members

Este pacote salvará os membros por instância.

const pvt = require('private-members');
const _ = pvt();

let Exemplo = (function () {    
    function Exemplo() {
        _(this).msg = "Minha Mensagem";
    }

    _().mensagem = function() {
        return _(this).msg;
    }

    Exemplo.prototype.showMsg = function () {
        let msg = _(this).mensagem();
        console.log(msg);
    };

    return Exemplo;
})();

module.exports = Exemplo;




es2015