tipos - herencia de clases javascript
Propiedades privadas en clases de JavaScript ES6 (30)
Un enfoque diferente al "privado"
En lugar de luchar contra el hecho de que la visibilidad privada no está disponible actualmente en ES6, decidí adoptar un enfoque más práctico que funciona bien si su IDE es compatible con JSDoc (por ejemplo, Webstorm). La idea es usar la etiqueta @private
. En lo que respecta al desarrollo, el IDE le impedirá acceder a cualquier miembro privado fuera de su clase. Funciona bastante bien para mí y ha sido realmente útil para ocultar métodos internos, por lo que la función de autocompletar me muestra exactamente lo que la clase realmente quería exponer. Aquí hay un ejemplo:
¿Es posible crear propiedades privadas en clases ES6?
Aquí hay un ejemplo. ¿Cómo puedo evitar el acceso a instance.property
?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
Actualización: Una propuesta con una sintaxis más agradable está en camino. Las contribuciones son bienvenidas.
Sí, hay, para el acceso de ámbito en los objetos, ES6 introduce Symbol
s .
Los símbolos son únicos, no puede obtener acceso a uno desde el exterior, excepto con la reflexión (como los datos privados en Java / C #), pero cualquiera que tenga acceso a un símbolo en el interior puede usarlo para el acceso clave:
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
Completando @ d13 y los comentarios de @ johnny-oshika y @DanyalAytekin:
Supongo que en el ejemplo proporcionado por @ johnny-oshika podríamos usar funciones normales en lugar de funciones de flecha y luego .bind
con el objeto actual más un objeto _privates
como un parámetro al curry:
algo.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
Beneficios que se me ocurren:
- podemos tener métodos privados (
_greet
y_updateMessage
actúan como métodos privados siempre y cuando noexport
las referencias) - aunque no están en el prototipo, los métodos mencionados anteriormente ahorrarán memoria porque las instancias se crean una vez, fuera de la clase (en lugar de definirlas en el constructor)
- No perdemos ningún globo global ya que estamos dentro de un módulo.
- También podemos tener propiedades privadas usando el objeto
_privates
Algunos inconvenientes que se me ocurren:
- menos intuitivo
- uso mixto de sintaxis de clase y patrones de la vieja escuela (enlaces de objetos, variables de ámbito de función / módulo)
- enlaces duros: no podemos volver a vincular los métodos públicos (aunque podemos mejorar esto utilizando enlaces blandos ( https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#softening-binding ))
Un fragmento de código en ejecución se puede encontrar aquí: http://www.webpackbin.com/NJgI5J8lZ
Creo que es posible obtener ''lo mejor de ambos mundos'' usando cierres dentro de los constructores. Hay dos variaciones:
Todos los datos de los miembros son 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);
}
};
Algunos miembros son privados
NOTA: Es cierto que esto es feo. Si conoce una solución mejor, edite esta respuesta.
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);
}
};
De hecho, es posible utilizar símbolos y proxies. Utiliza los símbolos en el alcance de la clase y configura dos capturas en un proxy: una para el prototipo de la clase para que Reflect.ownKeys (instancia) o Object.getOwnPropertySymbols no regale sus símbolos, la otra es para el propio constructor por lo tanto, cuando se llama a un new ClassName(attrs)
, la instancia devuelta será interceptada y tendrá bloqueados los propios símbolos de propiedades. Aquí está el 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 así: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))
por eso necesitamos una trampa para estos objetos.
Depende de a quién le preguntes :-)
No se incluye ningún modificador de propiedad private
en la propuesta de clases Máximamente mínimas que parece haberse incorporado al borrador actual .
Sin embargo, podría haber soporte para nombres privados , lo que sí permite propiedades privadas, y probablemente también podrían usarse en las definiciones de clase.
La única forma de obtener verdadera privacidad en JS es a través del alcance, por lo que no hay manera de tener una propiedad que sea miembro de this
que solo sea accesible dentro del componente. La mejor manera de almacenar datos verdaderamente privados en ES6 es con un 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, esto es probablemente lento, y definitivamente feo, pero proporciona privacidad.
Tenga en cuenta que AUN ESO no es perfecto, porque Javascript es muy dinámico. Alguien aun podría hacer
var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
// Store ''this'', ''key'', and ''value''
return oldSet.call(this, key, value);
};
para capturar los valores a medida que se almacenan, por lo tanto, si quisiera tener un cuidado especial, tendría que capturar una referencia local a .set
y .get
para usar explícitamente en lugar de confiar en el prototipo reemplazable.
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));
}
}
La respuesta es no". Pero puedes crear acceso privado a propiedades como esta:
- Utilizar módulos. Todo en un módulo es privado, a menos que se haga público mediante el uso de la palabra clave de
export
. - Dentro de los módulos, use la función de cierre: http://www.kirupa.com/html5/closures_in_javascript.htm
(La sugerencia de que los Símbolos podrían usarse para garantizar la privacidad era cierta en una versión anterior de la especificación ES6 pero ya no es el caso: https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604.html y https://.com/a/22280202/1282216 . Para una discusión más larga sobre los símbolos y la privacidad, consulte: https://curiosity-driven.org/private-properties-in-javascript )
Los campos privados se están implementando en el estándar ECMA . Puedes comenzar a usarlos hoy con el preajuste de babel 7 y etapa 3. Ver el ejemplo de babel REPL .
class Something {
#property;
constructor(){
this.#property = "test";
}
}
const instance = new Something();
console.log(instance.property); //=> undefined
Para expandir la respuesta de @ loganfsmyth:
Los únicos datos verdaderamente privados en JavaScript siguen siendo variables de ámbito. No puede tener propiedades privadas en el sentido de las propiedades a las que se accede internamente de la misma forma que las propiedades públicas, pero puede usar variables de ámbito para almacenar datos privados.
Variables con alcance
El enfoque aquí es utilizar el alcance de la función constructora, que es privada, para almacenar datos privados. Para que los métodos tengan acceso a estos datos privados, también deben crearse dentro del constructor, lo que significa que los está recreando con cada instancia. Esta es una penalización por rendimiento y memoria, pero algunos creen que la penalización es aceptable. La penalización se puede evitar para los métodos que no necesitan acceso a datos privados agregándolos al prototipo como de costumbre.
Ejemplo:
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
Se puede usar un WeakMap para evitar el rendimiento del enfoque anterior y la penalización de memoria. WeakMaps asocia datos con Objetos (aquí, instancias) de tal manera que solo se puede acceder usando ese WeakMap. Por lo tanto, usamos el método de variables con alcance para crear un WeakMap privado, luego utilizamos ese WeakMap para recuperar datos privados asociados con this
. Esto es más rápido que el método de variables con ámbito porque todas sus instancias pueden compartir un solo WeakMap, por lo que no es necesario que vuelva a crear métodos solo para que accedan a sus propios WeakMaps.
Ejemplo:
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 ejemplo usa un Objeto para usar un WeakMap para múltiples propiedades privadas; también puede usar múltiples WeakMaps y usarlos como age.set(this, 20)
, o escribir un pequeño contenedor y usarlo de otra manera, como privateProps.set(this, ''age'', 0)
.
La privacidad de este enfoque podría, en teoría, ser violada al manipular el objeto WeakMap
global. Dicho esto, todo el JavaScript puede ser roto por globales destrozados. Nuestro código ya se basa en el supuesto de que esto no está sucediendo.
(Este método también se podría hacer con Map
, pero WeakMap
es mejor porque Map
creará pérdidas de memoria a menos que sea muy cuidadoso, y para este propósito, los dos no son diferentes).
Media respuesta: Símbolos de ámbito
Un símbolo es un tipo de valor primitivo que puede servir como un nombre de propiedad. Puede usar el método de variable con ámbito para crear un Símbolo privado, luego almacenar datos privados en this[mySymbol]
.
La privacidad de este método puede Object.getOwnPropertySymbols
utilizando Object.getOwnPropertySymbols
, pero es un poco difícil de hacer.
Ejemplo:
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)`.
Media respuesta: guiones bajos
El antiguo predeterminado, solo usa una propiedad pública con un prefijo de subrayado. Aunque no es una propiedad privada de ninguna manera, esta convención prevalece tanto que hace un buen trabajo comunicando que los lectores deben tratar la propiedad como privada, lo que a menudo hace el trabajo. A cambio de este lapso, obtenemos un enfoque que es más fácil de leer, más fácil de escribir y más rápido.
Ejemplo:
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.
Conclusión
A partir de ES2017, todavía no hay una forma perfecta de hacer propiedades privadas. Varios enfoques tienen pros y contras. Las variables con alcance son verdaderamente privadas; Los WeakMaps con ámbito son muy privados y más prácticos que las variables con alcance; Los símbolos de alcance son razonablemente privados y razonablemente prácticos; Los guiones bajos son a menudo bastante privados y muy prácticos.
Para futuras referencias de otros usuarios, estoy escuchando ahora que la recomendación es usar WeakMaps para guardar datos privados.
Aquí hay un ejemplo más claro y 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}`)
}
}
}
}
Personalmente, me gusta la propuesta del operador de vinculación ::
y luego la combinaría con la solución @ d13 mencionada, pero por ahora se queda con la respuesta de @d13 donde utiliza la palabra clave de export
para su clase y coloca las funciones privadas en el módulo.
Hay una solución más difícil que no se ha mencionado aquí, que sigue es un enfoque más funcional y le permitiría tener todos los apoyos / métodos privados dentro de la clase.
Privado.js
export const get = state => key => state[key];
export const set = state => (key,value) => { state[key] = value; }
Prueba.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
Se agradecerían los comentarios al respecto.
Respuesta corta, no, no hay soporte nativo para propiedades privadas con clases ES6.
Pero puede imitar ese comportamiento al no adjuntar las nuevas propiedades al objeto, sino mantenerlas dentro de un constructor de clase, y usar captadores y definidores para alcanzar las propiedades ocultas. Tenga en cuenta que los captadores y definidores obtienen una nueva definición en cada nueva instancia de la clase.
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; }
}
Usar módulos ES6 (inicialmente propuesto por @ d13) funciona bien para mí. No imita las propiedades privadas a la perfección, pero al menos puede estar seguro de que las propiedades que deberían ser privadas no se filtrarán fuera de su clase. Aquí hay un ejemplo:
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'');
}
};
Entonces el código consumidor puede verse así:
import Something from ''./something.js'';
const something = new Something(''Sunny day!'');
something.say();
something._message; // undefined
something._greet(); // exception
Actualización (Importante):
Como @DanyalAytekin se describe en los comentarios, estas propiedades privadas son estáticas, por lo tanto, de alcance global. Trabajarán bien cuando trabajen con Singletons, pero se debe tener cuidado con los objetos Transitorios. Extendiendo el ejemplo anterior:
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
Sí, puede crear una propiedad encapsulada , pero no se ha hecho con modificadores de acceso (público | privado) al menos no con ES6.
Aquí hay un ejemplo simple de cómo se puede hacer con ES6:
1 Crear clase usando class palabra de class
2 En su interior, el constructor declara la variable de ámbito de bloque utilizando las palabras reservadas const -> como son de ámbito de bloque, no se puede acceder a ellas desde el exterior (encapsulado)
3 Para permitir cierto control de acceso (definidores | getters) a esas variables, puede declarar el método de instancia dentro de su constructor usando: la this.methodName=function(){}
"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;
}
}
}
Ahora vamos a comprobarlo:
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
WeakMap
- soportado en IE11 (los símbolos no lo son)
- hard-private (los accesorios que usan Symbols son soft-private debido a
Object.getOwnPropertySymbols
) - puede verse realmente limpio (a diferencia de los cierres que requieren todos los accesorios y métodos en el constructor)
Primero, define una función para envolver WeakMap:
function Private() {
const map = new WeakMap();
return obj => {
let props = map.get(obj);
if (!props) {
props = {};
map.set(obj, props);
}
return props;
};
}
Luego, construye una referencia fuera de tu clase:
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: la clase no es compatible con IE11, pero se ve más limpia en el ejemplo.
Otra forma similar a las dos últimas publicadas.
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
Utilizo este patrón y siempre me ha funcionado.
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
Aquí, la variable myThing es privada y es parte del cierre:
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);
Creo que la respuesta de Benjamin es probablemente la mejor para la mayoría de los casos hasta que el lenguaje respalde de forma nativa las variables privadas.
Sin embargo, si por alguna razón necesita evitar el acceso Object.getOwnPropertySymbols()
, un método que he considerado usar es adjuntar una propiedad única, no configurable, no enumerable, que se puede usar como un identificador de propiedad para cada objeto en construcción. (como un único Symbol
, si aún no tiene alguna otra propiedad única como una id
). Luego simplemente mantenga un mapa de las variables "privadas" de cada objeto usando ese 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"
La ventaja potencial de este enfoque sobre el uso de un tiempo de acceso más rápidoWeakMap
es si el rendimiento se convierte en una preocupación.
En realidad es posible.
1. Primero, cree la clase y en el constructor devuelva la _public
función llamada .
2. En la _public
función llamada , pase la this
referencia (para obtener acceso a todos los métodos y accesorios privados) y todos los argumentos de constructor
(que se pasarán new Names()
)
3. En el _public
ámbito de la función también está la Names
clase con el acceso a this
(_this ) referencia de la Names
clase 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);
}
Encontré una solución muy simple, solo use Object.freeze()
. Por supuesto, el problema es que no se puede agregar nada al objeto más adelante.
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
Incluso Typescript no puede hacerlo. De su documentation :
Cuando un miembro se marca como privado, no se puede acceder a él desde fuera de su clase contenedora. Por ejemplo:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: ''name'' is private;
Pero transpilado en su documentation esto da:
var Animal = (function () {
function Animal(theName) {
this.name = theName;
}
return Animal;
}());
console.log(new Animal("Cat").name);
Así que su palabra clave "privada" es ineficaz.
La mayoría de las respuestas dicen que es imposible, o requieren que uses un WeakMap o un Símbolo, que son características de ES6 que probablemente requieran polietileno. ¡Sin embargo hay otra manera! Echa un vistazo a esto:
// 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
A este método lo llamo patrón de acceso . La idea esencial es que tenemos un cierre , una clave dentro del cierre, y creamos un objeto privado (en el constructor) al que solo se puede acceder si tiene la clave .
Si está interesado, puede leer más sobre esto en mi artículo . Usando este método, puede crear propiedades por objeto a las que no se puede acceder fuera del cierre. Por lo tanto, puede usarlos en constructor o prototipo, pero no en ningún otro lugar. No he visto este método usado en ninguna parte, pero creo que es realmente poderoso.
Llegando muy tarde a esta fiesta, pero llegué a la pregunta OP en una búsqueda, así que ... Sí, puedes tener propiedades privadas envolviendo la declaración de clase en un cierre
Hay un ejemplo de cómo tengo métodos privados en este código de código . En el fragmento a continuación, la clase Suscribible tiene dos funciones "privadas" process
y processCallbacks
. Cualquier propiedad se puede agregar de esta manera y se mantiene privada mediante el uso del cierre. La privacidad de la OMI es una necesidad rara si las preocupaciones están bien separadas y Javascript no necesita hincharse agregando más sintaxis cuando un cierre hace el trabajo.
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)}}}})();
Me gusta este enfoque porque separa bien las preocupaciones y mantiene las cosas verdaderamente privadas. El único inconveniente es la necesidad de usar ''self'' (o algo similar) para referirse a ''this'' en el contenido privado.
Me encontré con esta publicación cuando buscaba las mejores prácticas para "datos privados para clases". Se mencionó que algunos de los patrones tendrían problemas de rendimiento.
Reuní algunas pruebas jsperf basadas en los 4 patrones principales del libro en línea "Explorando ES6":
http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes
Las pruebas se pueden encontrar aquí:
https://jsperf.com/private-data-for-classes
En Chrome 63.0.3239 / Mac OS X 10.11.6, los mejores patrones de rendimiento fueron "Datos privados a través de entornos de construcción" y "Datos privados a través de una convención de nombres". Para mí, Safari funcionó bien para WeakMap pero Chrome no tan bien.
No sé el impacto de la memoria, pero el patrón para "entornos de constructor" que algunos habían advertido sería un problema de rendimiento fue muy eficaz.
Los 4 patrones básicos son:
Datos privados a través de entornos de construcción.
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();
Datos privados a través de entornos constructores 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();
Datos privados a través de una convención 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();
Datos privados a través de 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();
Datos privados a travé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();
Puede probar esto https://www.npmjs.com/package/private-members
Este paquete salvará a los miembros por instancia.
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;
Sí totalmente puede, y bastante fácilmente también. Esto se hace exponiendo sus variables y funciones privadas devolviendo el gráfico del objeto prototipo en el constructor. Esto no es nada nuevo, pero tómese un poco de js foo para comprender su elegancia. De esta manera no se utiliza el ámbito global, o los mapas débiles. Es una forma de reflexión incorporada al lenguaje. Dependiendo de cómo se aproveche esto; uno puede forzar una excepción que interrumpa la pila de llamadas, o enterrar la excepción como un undefined
. Esto se muestra a continuación, y puede leer más sobre estas características here
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)); //2
console.log(clazz.public2(2)); //2
console.log(clazz.public3(3)); //27
console.log(clazz.private(0)); //error
Vea esta respuesta para una solución de "clase" limpia y simple con una interfaz privada y pública y soporte para composición
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"