Propriedades privadas nas classes de JavaScript ES6
instance.property
?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
30 answers
Os campos privados (e métodos) estão a ser implementados na normaECMA . Você pode começar a usá-los hoje com babel 7 e Fase 3 predefinida.
class Something {
#property;
constructor(){
this.#property = "test";
}
#privateMethod() {
return 'hello world';
}
getPrivateMessage() {
return this.#privateMethod();
}
}
const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> hello world
Mas você poderia imitar esse comportamento não anexando as novas propriedades ao objeto, mas mantendo-as dentro de um construtor de classe, e usar getters e setters para alcançar as propriedades escondidas. Note que os getters e setters são redefinidos em cada nova instância da classe.
ES6
class Person {
constructor(name) {
var _name = name
this.setName = function(name) { _name = name; }
this.getName = function() { return _name; }
}
}
ES5
function Person(name) {
var _name = name
this.setName = function(name) { _name = name; }
this.getName = function() { return _name; }
}
Para expandir a resposta de @loganfsmyth:
Os únicos dados verdadeiramente privados em JavaScript ainda são as variáveis estudadas. Você não pode ter propriedades privadas no sentido de propriedades acessadas internamente da mesma forma que propriedades públicas, mas você pode usar variáveis escopadas para armazenar dados privados.
Variáveis analisadas
A abordagem aqui é usar o escopo da função construtora, que é privada, para armazenar dados privados. Para métodos de ter acesso a este privado os dados que eles devem ser criados dentro do construtor também, o que significa que você está recriando-os com cada instância. Esta é uma pena de desempenho e memória, mas alguns acreditam que a pena é aceitável. A penalidade pode ser evitada para métodos que não precisam de acesso a 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
Scoped WeakMap
Um mapa fraco pode ser usado para evitar o desempenho e penalidade de memória da abordagem anterior. Dados associados do smakmaps com objetos (aqui, instâncias) de tal forma que só pode ser acessado usando esse WeakMap. Então, usamos o método das variáveis escopadas para criar um WeakMap privado, então usamos esse WeakMap para recuperar dados privados associados a this
. Isto é mais rápido do que o método das variáveis escopadas porque todas as suas instâncias podem compartilhar um único WeakMap, então 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 objecto 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 uma pequena embalagem e usá-la de outra forma, como privateProps.set(this, 'age', 0)
.
(este método também pode ser feito com Map
, mas WeakMap
é melhor porque Vai criar fugas de memória a não ser que tenhas muito cuidado, e para isso os dois não são diferentes.)
Meia Resposta: Símbolos Escopados
Um símbolo é um tipo de valor primitivo que pode servir como um nome de propriedade. Você pode usar o método da variável escopo para criar um símbolo privado,em seguida, armazenar dados privados em this[mySymbol]
.
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 antigo padrão, basta usar uma propriedade pública com um prefixo sublinhado. Embora não seja uma propriedade privada de qualquer forma, esta convenção é prevalente o suficiente para que faça um bom trabalho comunicando que os leitores devem tratar a propriedade como privada, o que muitas vezes faz o trabalho feito. Em troca deste lapso, obtemos uma abordagem que é mais fácil de ler, mais fácil de digitar, e rapidamente.
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 de ES2017, ainda não há uma forma perfeita de fazer propriedades privadas. Várias abordagens têm prós e contras. Escopo de variáveis são verdadeiramente privado; escopo WeakMaps são muito particulares e mais prático do que com escopo de variáveis; escopo Símbolos são razoavelmente privado e razoavelmente prático; sublinhados são, muitas vezes, privados suficiente e muito prático.Actualização: uma proposta com uma sintaxe melhor está a caminho. As contribuições são bem-vindas.
Sim, há - para acesso escopo em objectos - ES6 introduz Symbol
s .
Os símbolos são únicos, você não pode ter acesso a um de fora excepto com reflexão (como as partes privadas em Java/C#), mas qualquer um que tem acesso a um símbolo no interior pode usá-lo para acesso à chave:
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
- utilizar módulos. Tudo num módulo é privado, a menos que seja tornado público usando a palavra-chave
export
. - dentro dos módulos, usar o fecho da função: http://www.kirupa.com/html5/closures_in_javascript.htm
A única maneira de obter a verdadeira privacidade em JS é através de escopo, então não há maneira de ter uma propriedade que é um membro de this
que será acessível apenas dentro do componente. A melhor maneira de armazenar dados verdadeiramente privados em 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));
}
}
É óbvio que isto é provavelmente lento, e definitivamente feio, mas proporciona privacidade.
Lembre-se que nem isto é 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 apanhar valores à medida que são armazenados, por isso, se quiser ter um cuidado extra, terá de capturar uma referência local a .set
e .get
para usar explicitamente em vez de depender do protótipo que pode ser substituído.
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));
}
}
Para futura referência de outros sobre lookers, estou a ouvir agora que a recomendação é usar WeakMaps para guardar 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}`)
}
}
}
}
Depende de a quem perguntas. :-)
O modificador de propriedades está incluído no classes máximas mínimas proposta que parece ter chegado ao projecto actual.
No entanto, pode haver apoio para nomes privados, o que permite propriedades privadas - e eles provavelmente poderiam ser usados em Definições de classe também.Usar módulos ES6 (inicialmente propostos por @d13) funciona bem para mim. Ele não imita propriedades privadas perfeitamente, mas pelo menos você pode estar confiante de que propriedades que devem ser privadas não vai vazar fora de sua classe. Aqui está um exemplo:
Alguma coisa.jslet _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
Actualização (Importante):
Como @DanyalAytekin delineou nos comentários, Estas propriedades privadas são estáticas, portanto, de âmbito global. Eles vão trabalhar bem ao trabalhar com Singletons, mas cuidado deve ser tomado para objetos transitórios. Extensão do 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 propriedades encapsuladas, mas isso não foi feito com modificadores de acesso (público|privado) pelo menos não com o ES6.
Aqui está um exemplo simples de como pode ser feito com ES6:
1 Criar classe usando classe palavra
2 dentro do seu construtor declara a variável em bloco usando let ou const as palavras reservadas -> uma vez que são em bloco-escopo não podem ser acedidas a partir do exterior (encapsuladas)
3 a permitir algum controlo de acesso (setters|getters) a essas variáveis você pode declarar o método de instância dentro do 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
Completar @d13 e os comentários de @johnny-oshika e @DanyalAytekin:
Acho que no exemplo fornecido por @johnny-oshika poderíamos usar funções normais em vez de funções de seta e depois {[[2]} com o objecto actual mais um objecto _privates
como um parâmetro de Carriagem:
Alguma coisa.js
function _greet(_privates) {
return 'Hello ' + _privates.message;
}
function _updateMessage(_privates, newMessage) {
_privates.message = newMessage;
}
export default class Something {
constructor(message) {
const _privates = {
message
};
this.say = _greet.bind(this, _privates);
this.updateMessage = _updateMessage.bind(this, _privates);
}
}
Main.js
import Something from './something.js';
const something = new Something('Sunny day!');
const message1 = something.say();
something.updateMessage('Cloudy day!');
const message2 = something.say();
console.log(message1 === 'Hello Sunny day!'); // true
console.log(message2 === 'Hello Cloudy day!'); // true
// the followings are not public
console.log(something._greet === undefined); // true
console.log(something._privates === undefined); // true
console.log(something._updateMessage === undefined); // true
// another instance which doesn't share the _privates
const something2 = new Something('another Sunny day!');
const message3 = something2.say();
console.log(message3 === 'Hello another Sunny day!'); // true
Benefícios que me lembro:
- podemos ter métodos privados (
_greet
e_updateMessage
agir como métodos privados, desde que não façamos as referências. Apesar de não estarem no protótipo, os métodos acima mencionados salvarão a memória porque as instâncias são criadas uma vez, fora da classe (em oposição a defini-las no construtor) {[[27]} Não divulgamos nenhum Globo, já que estamos dentro de um módulo. - nós também podemos ter propriedades privadas usando o objeto binded
_privates
Posso pensar em alguns inconvenientes.:
- Menos intuitivo
- utilização mista da sintaxe da classe e dos padrões antigos da escola (atalhos de objectos, variáveis escopadas por módulos/funções)
- ligações duras-não podemos refazer os métodos públicos (embora possamos melhorar isto usando ligações macias(https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#softening-binding))
Um excerto em execução pode ser encontrado aqui: http://www.webpackbin.com/NJgI5J8lZ
Uma abordagem diferente de "privado"
Em vez de lutar contra o fato de que a visibilidade privada está atualmente indisponível no ES6, decidi tomar uma abordagem mais prática que faz muito bem se o seu IDE suporta JSDoc (por exemplo, Webstorm). A ideia é usar o @private
tag . No que diz respeito ao desenvolvimento, a IDE impedirá o acesso a qualquer membro privado de fora da sua classe. Funciona muito bem para mim e tem sido muito útil para esconder métodos internos o recurso Auto-completo mostra-me o que a classe realmente significava expor. Aqui está um exemplo:
class jobImpl{
// public
constructor(name){
this.name = name;
}
// public
do(time){
console.log(`${this.name} started at ${time}`);
this.prepare();
this.execute();
}
//public
stop(time){
this.finish();
console.log(`${this.name} finished at ${time}`);
}
// private
prepare(){ console.log('prepare..'); }
// private
execute(){ console.log('execute..'); }
// private
finish(){ console.log('finish..'); }
}
function Job(name){
var impl = new jobImpl(name);
return {
do: time => impl.do(time),
stop: time => impl.stop(time)
};
}
// Test:
// create class "Job"
var j = new Job("Digging a ditch");
// call public members..
j.do("08:00am");
j.stop("06:00pm");
// try to call private members or fields..
console.log(j.name); // undefined
j.execute(); // error
Outra Possível implementação da função (construtor)) Job
:
function Job(name){
var impl = new jobImpl(name);
this.do = time => impl.do(time),
this.stop = time => impl.stop(time)
}
WeakMap
- suportado na IE11 (Os símbolos não são)
- hard-private (os adereços que usam símbolos são soft-private devido a
Object.getOwnPropertySymbols
) - pode parecer realmente limpo (ao contrário dos fechos que requerem todos os adereços e métodos no construtor) {[[10]}
Primeiro, defina uma função para envolver o WeakMap:
function Private() {
const map = new WeakMap();
return obj => {
let props = map.get(obj);
if (!props) {
props = {};
map.set(obj, props);
}
return props;
};
}
Então, construa uma referência fora da sua classe.
const p = new Private();
class Person {
constructor(name, age) {
this.name = name;
p(this).age = age; // it's easy to set a private variable
}
getAge() {
return p(this).age; // and get a private variable
}
}
Nota: a classe não é suportada pela IE11, mas parece mais limpa no exemplo.
::
e então combiná-lo-ia com a solução @D13 mencionada, mas por agora fique com a resposta de @d13 onde você usa a palavra-chave export
para a sua classe e coloque as funções privadas no módulo.
Há mais uma solução difícil que não foi mencionado aqui que segue é uma abordagem mais funcional e lhe permitiria ter todos os adereços/métodos privados dentro do classe.
Soldado.js
export const get = state => key => state[key];
export const set = state => (key,value) => { state[key] = value; }
Teste.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
Agradecia que comentasse o assunto.
Eu acho que a resposta de Benjamin é provavelmente a melhor para a maioria dos casos até que a linguagem nativamente suporta variáveis explicitamente privadas.
No entanto, se, por alguma razão, tiver de impedir o acesso com Object.getOwnPropertySymbols()
, um método que eu considerei usar é anexar uma propriedade única, não-configurável, não-enumerável, não-escrita que pode ser usada como um identificador de propriedade para cada objeto na construção (como um único Symbol
, Se você já não tem algum outro único propriedade como um ([3]}). Em seguida, basta manter um mapa das variáveis 'privadas' de cada objeto usando esse identificador.
const privateVars = {};
class Something {
constructor(){
Object.defineProperty(this, '_sym', {
configurable: false,
enumerable: false,
writable: false,
value: Symbol()
});
var myPrivateVars = {
privateProperty: "I'm hidden"
};
privateVars[this._sym] = myPrivateVars;
this.property = "I'm public";
}
getPrivateProperty() {
return privateVars[this._sym].privateProperty;
}
// A clean up method of some kind is necessary since the
// variables won't be cleaned up from memory automatically
// when the object is garbage collected
destroy() {
delete privateVars[this._sym];
}
}
var instance = new Something();
console.log(instance.property); //=> "I'm public"
console.log(instance.privateProperty); //=> undefined
console.log(instance.getPrivateProperty()); //=> "I'm hidden"
A vantagem potencial desta abordagem sobre a utilização de uma WeakMap
é tempo de acesso mais rápido se o desempenho se tornar uma preocupação.
Http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes
Os testes podem ser encontrados. toma.
Https://jsperf.com/private-data-for-classes
In Chrome 63.0.3239 / Mac OS X 10.11.6, the best performing patterns were " Private data via constructor environments "and"Private data via a naming convention". Para mim Safari correu bem para o WeakMap, mas o cromo não tão bem.
Não sei o impacto da memória, mas o padrão para "ambientes Construtores" que alguns avisaram que seria um problema de desempenho era muito performativo.O 4 básico os padrões são:
Dados privados através de ambientes de construção
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 construção 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 através 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 através de mapas fracos
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 através 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();
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, por favor, edite esta 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);
}
};
new ClassName(attrs)
for chamado, a instância devolvida será interceptada e terá os símbolos de propriedades bloqueados.
Aqui está o ... Código:
const Human = (function() {
const pet = Symbol();
const greet = Symbol();
const Human = privatizeSymbolsInFn(function(name) {
this.name = name; // public
this[pet] = 'dog'; // private
});
Human.prototype = privatizeSymbolsInObj({
[greet]() { // private
return 'Hi there!';
},
revealSecrets() {
console.log(this[greet]() + ` The pet is a ${this[pet]}`);
}
});
return Human;
})();
const bob = new Human('Bob');
console.assert(bob instanceof Human);
console.assert(Reflect.ownKeys(bob).length === 1) // only ['name']
console.assert(Reflect.ownKeys(Human.prototype).length === 1 ) // only ['revealSecrets']
// Setting up the traps inside proxies:
function privatizeSymbolsInObj(target) {
return new Proxy(target, { ownKeys: Object.getOwnPropertyNames });
}
function privatizeSymbolsInFn(Class) {
function construct(TargetClass, argsList) {
const instance = new TargetClass(...argsList);
return privatizeSymbolsInObj(instance);
}
return new Proxy(Class, { construct });
}
Reflect.ownKeys()
funciona assim: é por isso que precisamos de uma armadilha para estes objectos.
Quando um membro é marcado como privado, não pode ser acedido de fora da sua classe que contém. Por exemplo:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: 'name' is private;
Mas transpiled no seu recreio Isto dá:
var Animal = (function () {
function Animal(theName) {
this.name = theName;
}
return Animal;
}());
console.log(new Animal("Cat").name);
Então a palavra-chave" privada " deles é ineficaz.
Há um exemplo de como tenho métodos privados neste codepen. No excerto abaixo, a classe Subescritível tem duas funções 'privadas' process
e processCallbacks
. Todas as propriedades podem ser adicionadas desta forma e elas são mantidas privadas através do uso do fechamento. A privacidade da OMI é uma necessidade rara se preocupações são bem separadas e Javascript não precisa se tornar inchado, adicionando mais sintaxe quando um fechamento faz bem 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)}}}})();
Gosto desta abordagem porque separa as preocupações e mantém as coisas verdadeiramente privadas. A única desvantagem é a necessidade de usar "self" (ou algo semelhante) para se referir a "isto" no conteúdo privado.
undefined
. Presente é demonstrado abaixo, e pode ler mais sobre estas características aqui
class Clazz {
constructor() {
var _level = 1
function _private(x) {
return _level * x;
}
return {
level: _level,
public: this.private,
public2: function(x) {
return _private(x);
},
public3: function(x) {
return _private(x) * this.public(x);
},
};
}
private(x) {
return x * x;
}
}
var clazz = new Clazz();
console.log(clazz._level); //undefined
console.log(clazz._private); // undefined
console.log(clazz.level); // 1
console.log(clazz.public(1)); //1
console.log(clazz.public2(2)); //2
console.log(clazz.public3(3)); //27
console.log(clazz.private(0)); //error
class Something {
constructor(){
var _property = "test";
Object.defineProperty(this, "property", {
get: function(){ return _property}
});
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
instance.property = "can read from outside, but can't write";
console.log(instance.property); //=> "test"
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
Ao ler a resposta anterior, pensei que este exemplo pudesse resumir as soluções acima
const friend = Symbol('friend');
const ClassName = ((hidden, hiddenShared = 0) => {
class ClassName {
constructor(hiddenPropertyValue, prop){
this[hidden] = hiddenPropertyValue * ++hiddenShared;
this.prop = prop
}
get hidden(){
console.log('getting hidden');
return this[hidden];
}
set [friend](v){
console.log('setting hiddenShared');
hiddenShared = v;
}
get counter(){
console.log('getting hiddenShared');
return hiddenShared;
}
get privileged(){
console.log('calling privileged method');
return privileged.bind(this);
}
}
function privileged(value){
return this[hidden] + value;
}
return ClassName;
})(Symbol('hidden'), 0);
const OtherClass = (() => class OtherClass extends ClassName {
constructor(v){
super(v, 100);
this[friend] = this.counter - 1;
}
})();
Actualizar
Agora é possível fazer verdadeiras propriedades privadas e métodos (pelo menos em navegadores baseados em cromos por agora).
A sintaxe é muito limpa
class MyClass {
#privateProperty = 1
#privateMethod() { return 2 }
static #privateStatic = 3
static #privateStaticMethod(){return 4}
static get #privateStaticGetter(){return 5}
// also using is quite straightforward
method(){
return (
this.#privateMethod() +
this.#privateProperty +
MyClass.#privateStatic +
MyClass.#privateStaticMethod() +
MyClass.#privateStaticGetter
)
}
}
new MyClass().method()
// returns 15
Note que para obter referências estáticas você não usaria {[[2]}, porque iria travar as suas subclasses. Você deve usar uma referência à classe adequada, a fim de recuperar suas referências privadas estáticas (que estão disponíveis apenas dentro dos métodos dessa classe), ie MyClass.#private
.
A maioria das respostas ou dizem que é impossível, ou exigem que você use um WeakMap ou símbolo, que são características ES6 que provavelmente necessitariam de polifills. Mas há outra maneira! Olha para isto.
// 1. Create closure
var SomeClass = function() {
// 2. Create `key` inside a closure
var key = {};
// Function to create private storage
var private = function() {
var obj = {};
// return Function to access private storage using `key`
return function(testkey) {
if(key === testkey) return obj;
// If `key` is wrong, then storage cannot be accessed
console.error('Cannot access private properties');
return undefined;
};
};
var SomeClass = function() {
// 3. Create private storage
this._ = private();
// 4. Access private storage using the `key`
this._(key).priv_prop = 200;
};
SomeClass.prototype.test = function() {
console.log(this._(key).priv_prop); // Using property from prototype
};
return SomeClass;
}();
// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged
// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged
Eu chamo a este método Padrão de acesso . A ideia essencial é que temos umFecho , Umachave dentro do fecho, e criamos um objecto privado (no construtor) que só pode ser acedido se tiver a chave.
Se estiver interessado, pode ler mais sobre este assunto no meu artigo . Usando este método, você pode criar propriedades por objeto que não podem ser acessadas fora do fechamento. Portanto, você pode usá-los em construtor ou protótipo, mas não em qualquer outro lugar. Não vi este método em lado nenhum, mas acho que é muito poderoso.
Ver esta resposta para uma solução 'class' limpa e simples com uma interface privada e pública e suporte para a composição
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 uso este 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
1. Primeiro, crie a classe e no construtor retorne a chamada função
_public
.2. Na chamada função
_public
passar a referência this
(para obter o acesso a todos os métodos e adereços privados), e todos os argumentos de constructor
(Isso será passado new Names()
)3. No âmbito da função
_public
Existe também a classe Names
com acesso a this
(_this) referência da classe Names
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);
}