Propiedades privadas en clases de JavaScript ES6


444

¿Es posible crear propiedades privadas en las clases de 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"

55
En realidad, existe una propuesta de etapa 3 para esta función: tc39.github.io/proposal-class-fields github.com/tc39/proposal-class-fields
arty

@arty He proporcionado una respuesta a esto con ejemplos: stackoverflow.com/a/52237988/1432509
Alister el

Respuestas:


165

Se están implementando campos privados (y métodos) en el estándar ECMA . Puede comenzar a usarlos hoy con preajuste de babel 7 y etapa 3.

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

Me pregunto cómo pueden funcionar esos campos de clase. Actualmente no puede usar thisen constructor antes de llamar super(). Sin embargo, Babel los pone antes que Super.
seeker_of_bacon

Cómo configurar ESLint para permitir #privateCrap sintaxis?
Marecky

66
¿Y qué hay de Eslint? Obtuve un error del analizador en el signo igual. Babel está funcionando, solo eslint no puede analizar esta nueva sintaxis js.
Martonx

66
Wow esto es muy feo. Hashtag es un personaje válido. La propiedad no es realmente privada, o? .. Lo revisé en TypeScript. Los miembros privados no se compilan en privado o de solo lectura (desde fuera). Acaba de declararse como otra propiedad (pública). (ES5).
Dominik

2
¿Cómo se escriben métodos privados con esto? ¿Puedo hacer esto #beep() {}? y esto async #bzzzt() {}:?
Константин Ван

277

Respuesta corta, no, no hay soporte nativo para propiedades privadas con clases ES6.

Pero podría imitar ese comportamiento al no asociar las nuevas propiedades al objeto, sino al mantenerlas dentro de un constructor de clases, y utilizar captadores y definidores para alcanzar las propiedades ocultas. Tenga en cuenta que los getters y setters se redefinen 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; }
}

1
Me gusta esta solución lo mejor. Estoy de acuerdo en que no debe usarse para escalar, pero es perfecto para clases que generalmente solo se instanciarán una vez por inclusión.
Blake Regalia

2
También está redefiniendo cada componente de esta clase cada vez que se crea un nuevo.
Quentin Roy

10
¡Esto es tan extraño! ¡En ES6 está creando más "pirámides de cierre" que antes de ES6! La definición de funciones DENTRO de un constructor se ve más fea que en el ejemplo ES5 anterior.
Kokodoko el

1
Dado que el OP pregunta específicamente sobre las clases de ES6, personalmente creo que esta es una solución pobre, aunque técnicamente funciona. La principal limitación es que ahora cada método de clase que utiliza variables privadas debe declararse dentro del constructor, lo que socava gravemente las ventajas de tener classsintaxis en primer lugar.
NanoWizard

10
Todo lo que hace es introducir indirección. Ahora, ¿cómo hacer que las propiedades getNamey sean setNameprivadas?
aij

195

Para ampliar la respuesta de @ loganfsmyth:

Los únicos datos verdaderamente privados en JavaScript siguen siendo las variables de ámbito. No puede tener propiedades privadas en el sentido de propiedades a las que se accede internamente de la misma manera que las propiedades públicas, pero puede usar variables de ámbito para almacenar datos privados.

Variables de 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 de rendimiento y memoria, pero algunos creen que la penalización es aceptable. La penalización se puede evitar para 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

Scoak WeakMap

Se puede usar un WeakMap para evitar el rendimiento del enfoque anterior y la penalización de la 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 ámbito para crear un WeakMap privado, luego usamos ese WeakMap para recuperar datos privados asociados conthis . 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 necesita volver 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 puedes usar múltiples WeakMaps y usarlos comoage.set(this, 20) , o escribir un pequeño contenedor y usarlo de otra manera, comoprivateProps.set(this, 'age', 0) .

La privacidad de este enfoque podría ser violada teóricamente al alterar el WeakMap objeto . Dicho esto, todo el JavaScript puede ser roto por los globals destrozados. Nuestro código ya está basado en el supuesto de que esto no está sucediendo.

(Este método también podría hacerse Map, pero WeakMapes mejor porqueMap 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 con ámbito

Un símbolo es un tipo de valor primitivo que puede servir como nombre de propiedad. Puede usar el método de variable con ámbito para crear un símbolo privado y luego almacenar datos privados enthis[mySymbol] .

La privacidad de este método se puede violar usando Object.getOwnPropertySymbols , pero es algo incómodo 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 valor predeterminado anterior es usar una propiedad pública con un prefijo de subrayado. Aunque no es una propiedad privada de ninguna manera, esta convención es lo suficientemente frecuente como para hacer un buen trabajo al comunicar 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 manera perfecta de hacer propiedades privadas. Varios enfoques tienen pros y contras. Las variables de ámbito son verdaderamente privadas; los WeakMaps con ámbito son muy privados y más prácticos que las variables con ámbito; Los símbolos de alcance son razonablemente privados y razonablemente prácticos; los guiones bajos suelen ser lo suficientemente privados y muy prácticos.


77
El primer fragmento de ejemplo ("variables de ámbito") es un antipatrón total: cada objeto devuelto tendrá una clase diferente. No hagas eso. Si desea métodos con privilegios, créelos en el constructor.
Bergi

1
Ajustar una clase dentro de una función parece anular el propósito de usar clases en primer lugar. Si ya usa la función para crear una instancia, también podría colocar a todos sus miembros privados / públicos dentro de esa función, y olvidarse de la palabra clave de toda la clase.
Kokodoko

2
@Bergi @Kokodoko Edité el enfoque de las variables de ámbito para que sea un poco más rápido y no se rompa instanceof. Admito que estaba pensando en ese enfoque como incluido solo por el bien de la integridad y debería haber pensado más en cuánto es realmente capaz de hacerlo.
tristan

1
Excelente explicación! Todavía me sorprende que ES6 en realidad dificulte la simulación de una variable privada, donde en ES5 podría usar var y esto dentro de una función para simular privado y público.
Kokodoko el

2
@Kokodoko Si prescindes de la clase y simplemente pones todo en la función, también tendrás que volver a implementar la herencia utilizando el método prototipo. Usar extender en clases es, con mucho, un enfoque más limpio, por lo que usar una clase dentro de una función es totalmente aceptable.
AndroidDev

117

Actualización: una propuesta con una sintaxis más agradable está en camino. Las contribuciones son bienvenidas.


Sí, hay - para el acceso con alcance en los objetos - ES6 introduce Symbols .

Los símbolos son únicos, no puede obtener acceso a uno desde el exterior excepto con reflejo (como elementos privados en Java / C #), pero cualquiera que tenga acceso a un símbolo en el interior puede usarlo para acceder a las teclas:

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

66
¿No puedes usar Object.getOwnPropertySymbols? ;)
Qantas 94 Heavy

41
@BenjaminGruenbaum: Al parecer, los símbolos ya no garantizan una verdadera privacidad: stackoverflow.com/a/22280202/1282216
d13

28
@trusktr a través de tres llaves? No. ¿A través de los símbolos? Si. Muy parecido a cómo puede usar la reflexión en lenguajes como C # y Java para acceder a campos privados. Los modificadores de acceso no se tratan de seguridad, sino de claridad de intenciones.
Benjamin Gruenbaum

99
Parece que usar Símbolos es similar a hacerlo const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();. Esto no es realmente privacidad, es oscuridad, en el sentido del JavaScript tradicional. Consideraría JavaScript "privado" como el uso de cierres para encapsular variables. Por lo tanto, esas variables no son accesibles a través de la reflexión.
trusktr

13
Además, creo que usar las palabras clave privatey protectedsería mucho más limpio que Symbolo Name. Prefiero la notación de puntos en lugar de la notación de corchetes. Me gustaría seguir usando un punto para cosas privadas. this.privateVar
trusktr

33

La respuesta es no". Pero puede crear acceso privado a propiedades como esta:

(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 así: https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604. html y https://stackoverflow.com/a/22280202/1282216 . Para una discusión más larga sobre Símbolos y privacidad ver: https://curiosity-driven.org/private-properties-in-javascript )


66
-1, esto realmente no responde a tu pregunta. (También puede usar cierres con IIFE en ES5). Las propiedades privadas son enumerables a través de la reflexión en la mayoría de los lenguajes (Java, C #, etc.). El objetivo de las propiedades privadas es transmitir la intención a otros programadores y no hacer cumplir la seguridad.
Benjamin Gruenbaum

1
@BenjaminGruenbaum, lo sé, desearía tener una mejor respuesta, tampoco estoy contento con eso.
d13

Creo que los símbolos siguen siendo una forma válida de lograr miembros inaccesibles en el entorno de programación. Sí, todavía se pueden encontrar si realmente quieres, pero ese no es el punto, ¿verdad? No debe almacenar información confidencial en él, pero no debe hacerlo de todos modos en el código del lado del cliente. Pero funciona con el propósito de ocultar una propiedad o método de una clase externa.
Kokodoko

El uso de variables en el ámbito de un módulo como sustituto de las propiedades privadas en una clase dará lugar a un comportamiento o comportamiento único similar a las propiedades estadísticas. Se compartirán las instancias de vars.
Adrian Moisa

30

La única forma de obtener una verdadera privacidad en JS es a través del alcance, por lo que no hay forma de tener una propiedad que sea miembro de la thisque solo se pueda acceder 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 un proceso lento y definitivamente feo, pero proporciona privacidad.

Tenga en cuenta que AUN ESTO no es perfecto, porque Javascript es muy dinámico. Alguien todavía 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 que si desea ser más cuidadoso, necesitaría capturar una referencia local .sety .getusarla 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));
  }
}

3
Como sugerencia, puede evitar usar un mapa débil por propiedad usando un objeto como valor. De esta manera, también puede reducir el número de mapas geta uno por método (por ejemplo const _ = privates.get(this); console.log(_.privateProp1);).
Quentin Roy

Sí, eso es totalmente una opción también. La mayoría de las veces fui con esto, ya que se asigna más directamente a lo que un usuario habría escrito al usar propiedades reales.
loganfsmyth

@loganfsmyth const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1"que significa que su propiedad es privada o no?
Barbu Barbu

2
Para que eso funcione, el código de acceso a la propiedad necesitaría acceso al objeto WeakMap, que normalmente estaría dentro de un módulo y
sería

22

Para futuras referencias de otros usuarios, escucho 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}`)
      }
    }
  }
}

20
Tenga en cuenta que estas propiedades son estáticas.
Michael Theriot

8
No te rechacé pero tu ejemplo de mapa débil está completamente equivocado.
Benjamin Gruenbaum

44
A saber: está compartiendo los datos entre todas las instancias de clase y no por instancia, ¿puedo al menos arreglarlo?
Benjamin Gruenbaum

1
De hecho, el mapa débil debe adjuntarse a una instancia determinada. Consulte fitzgeraldnick.com/weblog/53 para ver un ejemplo.
widged

2
Según MDN, los tipos de datos primitivos, como los símbolos, no están permitidos como clave WeakMap. Documentación de MDN WeakMap
leepowell

12

Depende de a quién le preguntes :-)

No privatese incluye ningún modificador de propiedad en la propuesta de Clases mínimamente máximas que parece haber llegado al borrador actual .

Sin embargo, puede haber soporte para nombres privados , lo que sí permite propiedades privadas, y probablemente también podrían usarse en definiciones de clase.


3
Es muy poco probable que los nombres privados lleguen a ES6, aunque están pensando en alguna forma de cosa privada para ES7.
Qantas 94 Heavy

@ Qantas94Heavy tanto los nombres privados como los valores de cadena únicos han sido reemplazados por Símbolos por lo que entiendo.
Benjamin Gruenbaum

Sí, probablemente se convertirá en símbolos. Sin embargo, afaik los "símbolos" actualmente contenidos en la especificación solo se usan para describir propiedades internas como [[prototipo]], y no hay forma de crearlos y usarlos en el código de usuario. ¿Conoces algunos documentos?
Bergi

Me acabo de dar cuenta de que los módulos se pueden usar para establecer la privacidad. ¿Combinado con símbolos que podrían ser todo lo que necesitarías ...?
d13

1
@Cody: el código completo de su módulo tiene su propio alcance en ES6 de todos modos, no es necesario un IEFE. Y sí, los símbolos están destinados a la unicidad (evitar colisiones), no a la privacidad.
Bergi

10

Usar módulos ES6 (inicialmente propuesto por @ d13) funciona bien para mí. No imita perfectamente las propiedades privadas, 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 describió en los comentarios, estas propiedades privadas son estáticas, por lo tanto, de alcance global. Funcionarán bien cuando trabajen con Singletons, pero se debe tener cuidado con los objetos transitorios. Ampliando 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

44
Bueno para private static.
Danyal Aytekin el

@DanyalAytekin: ese es un muy buen punto. Estas propiedades privadas son estáticas, de alcance global. He actualizado mi respuesta para reflejar esto.
Johnny Oshika

Cuanto más aprendo sobre la programación funcional (especialmente Elm y Haskell), más creo que los programadores de JS se beneficiarían de un enfoque basado en módulos para la "modularidad" en lugar de uno basado en la clase OOP. Si pensamos en los módulos ES6 como la base para crear aplicaciones, y nos olvidamos por completo de las clases, creo que podemos terminar con aplicaciones mucho mejores en general. ¿Podría algún usuario experimentado de Elm o Haskell comentar sobre este enfoque?
d13

1
En la actualización, el segundo a.say(); // adebería serb.say(); // b
grokky

de let _message = nullmanera probada , no tan genial, cuando llama al constructor varias veces, se equivoca.
Littlee

9

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 .bindusarlas con el objeto actual más un _privatesobjeto como parámetro 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 en los que puedo pensar:

  • podemos tener métodos privados ( _greety _updateMessageactuar como métodos privados siempre que no tengamos exportlas 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 filtramos ningún globals ya que estamos dentro de un módulo
  • también podemos tener propiedades privadas usando el _privatesobjeto enlazado

Algunos inconvenientes en los que puedo pensar:

Puede encontrar un fragmento en ejecución aquí: http://www.webpackbin.com/NJgI5J8lZ


8

Sí, puede crear propiedades encapsuladas , pero no se ha hecho con modificadores de acceso (públicos | privados) 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

2 En su interior, el constructor declara la variable de ámbito de bloque utilizando let OR const palabras reservadas -> dado que son de ámbito de bloque, no se puede acceder desde afuera (encapsulado)

3 Para permitir cierto control de acceso (setters | getters) a esas variables, puede declarar el método de instancia dentro de su constructor usando: this.methodName=function(){}sintaxis

"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

1
Esta es (por ahora) la única solución a este problema a pesar del hecho de que todos los métodos declarados en el constructor se vuelven a declarar para cada instancia de la clase. Esta es una muy mala idea con respecto al rendimiento y el uso de memoria. Los métodos de clase deben declararse fuera del alcance del constructor.
Freezystem

@Freezystem Primero: Primero, esos son métodos de instancia (no métodos de Clase). Segunda pregunta era OP: _ ¿Cómo puedo evitar el acceso a instance.property?_ y mi respuesta es: un ejemplo de cómo ... En tercer lugar , si usted tiene alguna idea mejor - oigámoslo
Nikita Kurtin

1
No estaba diciendo que estaba equivocado, dije que su solución era el mejor compromiso para lograr una variable privada a pesar del hecho de que se crea una copia de cada método de instancia cada vez que llama new Something();porque sus métodos se declaran en el constructor para tener acceso a estos variables privadas Eso puede causar un gran consumo de memoria si crea muchas instancias de su clase, por lo que hay problemas de rendimiento. Los métodos deberían haberse declarado fuera del alcance del constructor. Mi comentario fue más una explicación de los inconvenientes de su solución que una crítica.
Freezystem

1
Pero, ¿no es una mala práctica definir toda tu clase dentro del constructor? ¿No estamos simplemente "hackeando" javascript ahora? Simplemente observe cualquier otro lenguaje de programación OOP, y verá que el constructor no está destinado a definir una clase.
Kokodoko el

1
Sí, a eso me refería, ¡y su solución funciona! Solo digo que, en general, me sorprende que ES6 haya agregado una palabra clave de 'clase', pero eliminó la elegante solución de trabajar con var y esto, para lograr la encapsulación.
Kokodoko el

8

Un enfoque diferente a "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 funcione bien si su IDE admite JSDoc (por ejemplo, Webstorm). La idea es usar la @privateetiqueta . En cuanto al desarrollo, el IDE le impedirá acceder a cualquier miembro privado desde 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:

autocompletar mostrando solo cosas públicas


1
El problema es que no queremos acceder a las variables privadas a través del Editor, no queremos proteger las variables privadas del exterior. Y eso es lo que hace público / privado. Si su código está terminado, puede acceder (y lo importante: anular ) estas variables desde fuera de la clase. Su @privatecomentario no puede evitar esto, es solo una característica para la generación de documentación y su IDE.
Adrian Preuss

Sí, soy consciente de eso. Es solo que eso es suficiente para mí y puede ser suficiente para otras personas. Sé que en realidad no está haciendo que mis variables sean privadas; solo me advierte que no intente acceder desde afuera (solo, por supuesto, si mi equipo y yo estamos usando un IDE que admite esta función). Javascript (y otros lenguajes, como Python) no se diseñó teniendo en cuenta los niveles de acceso. La gente hace todo tipo de cosas para implementar de alguna manera esa funcionalidad, pero al final terminamos pirateando el lenguaje para lograrlo. Decidí ir con un enfoque más "natural", si quieres.
Lucio Paiva

6

WeakMap

  • compatible con IE11 (los símbolos no son)
  • hard-private (los accesorios que usan símbolos 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, defina 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, construya una referencia fuera de su 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: IE11 no admite la clase , pero se ve más limpia en el ejemplo.


6

¡Oh, tantas soluciones exóticas! Por lo general, no me importa la privacidad, así que uso "seudo privacidad" como se dice aquí . Pero si te importa (si hay algunos requisitos especiales para eso), uso algo como en este ejemplo:

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

Otra posible implementación de la función (constructor) Job:

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}

5

Personalmente, me gusta la propuesta del operador de :: vinculación y luego la combinaría con la solución @ d13 mencionada, pero por ahora quédese con la respuesta de @ d13 donde usa la exportpalabra clave 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 accesorios / métodos privados dentro de la clase.

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

Se agradecerán sus comentarios al respecto.


En general me gusta el enfoque. Comentarios: 1. necesitará un módulo private.js diferente para cada clase para evitar conflictos. 2. No me gusta el potencial de hacer que el constructor sea realmente largo definiendo en línea cada uno de sus métodos privados. 3. Sería bueno si todos los métodos de la clase estuvieran en un archivo.
Doug Coburn

5

Encontré 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 patrones de mejor desempeño fueron "Datos privados a través de entornos de constructor" y "Datos privados a través de una convención de nomenclatura". Para mí, Safari funcionó bien para WeakMap pero Chrome no tan bien.

No sé el impacto de la memoria, pero el patrón de "entornos de constructor" que algunos habían advertido que sería un problema de rendimiento fue muy eficaz.

Los 4 patrones básicos son:

Datos privados a través de entornos de constructor

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 de constructor 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();

4

Creo que es posible obtener 'lo mejor de ambos mundos' usando cierres dentro de los constructores. Hay dos variaciones:

Todos los miembros de datos 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: Esto es ciertamente 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);
   }
};


4

De hecho, es posible usar símbolos y proxies. Utiliza los símbolos en el alcance de la clase y establece dos trampas en un proxy: una para el prototipo de clase para que Reflect.ownKeys (instancia) u Object.getOwnPropertySymbols no revele sus símbolos, la otra es para el propio constructor así que cuando new ClassName(attrs)se llama, 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.


Gracias, probaré los símbolos :) De todas las respuestas anteriores, parece la forma más sencilla de crear un miembro de la clase inaccesible :)
Kokodoko

4

Incluso Typecript no puede hacerlo. De su documentación :

Cuando un miembro se marca como privado, no se puede acceder desde fuera de su clase que lo contiene. 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 patio de recreo esto da:

var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    return Animal;
}());
console.log(new Animal("Cat").name);

Por lo tanto, su palabra clave "privada" no es efectiva.


2
Bueno, sigue siendo eficaz porque evita la programación "mala", mientras está en el IDE. Le muestra qué miembros debe y no debe usar. Creo que esa es la razón principal para usar privado y público. (Por ejemplo, cuando compila C # en el código de la máquina, ¿lo privado seguirá siendo privado? ¿Quién sabe?). Al leer las otras respuestas, parece que usar @Symbol también puede hacer que un miembro sea inaccesible. Pero incluso los símbolos todavía se pueden encontrar desde la consola.
Kokodoko

¿Se produce el error TypeScript durante la transpilación de TypeScript a JavaScript? (Al igual que la verificación de tipo ocurre en el momento de la transferencia. En lugar de algún mecanismo privado de tiempo de ejecución.)
Eljay

4

Llegué muy tarde a esta fiesta, pero presioné la pregunta OP en una búsqueda, así que ... Sí, puede tener propiedades privadas envolviendo la declaración de clase en un cierre

Hay un ejemplo de cómo tengo métodos privados en este codepen . En el fragmento a continuación, la clase Suscribible tiene dos funciones 'privadas' processy processCallbacks. Cualquier propiedad se puede agregar de esta manera y se mantienen privadas mediante el uso del cierre. La privacidad de la OMI es una necesidad poco frecuente si las preocupaciones están bien separadas y no es necesario que Javascript se hinche agregando más sintaxis cuando un cierre funciona perfectamente.

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 realmente privadas. El único inconveniente es la necesidad de usar 'self' (o algo similar) para referirse a 'this' en el contenido privado.


4

Creo que la respuesta de Benjamin es probablemente la mejor para la mayoría de los casos hasta que el lenguaje admita de forma nativa variables explícitamente 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 y no grabable que se puede usar como un identificador de propiedad para cada objeto en la construcción (como una única Symbol, si aún no tiene 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 a WeakMapes un tiempo de acceso más rápido si el rendimiento se convierte en una preocupación.


1
Corríjame si me equivoco, pero ¿no contendría este código pérdidas de memoria ya que privateVars aún almacenará las variables privadas de un objeto incluso si el objeto ya está destruido?
Russell Santos

@RussellSantos tiene razón, suponiendo que los objetos tendrán que ser recolectados en algún momento. Gracias por señalar eso. En mi ejemplo, agregué un destroy()método que debería ser llamado por el código de uso cada vez que un objeto necesita ser eliminado.
NanoWizard

4

Sí, totalmente puede, y muy 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 toma un poco de js foo para entender la elegancia del mismo. De esta manera, no se utiliza un ámbito global o mapas débiles. Es una forma de reflexión integrada en el lenguaje. Dependiendo de cómo aproveche esto; uno puede forzar una excepción que interrumpe 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 aquí.

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


3
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"

2
Es mejor evitar las respuestas de solo código. Sería mejor si pudiera explicar cómo su código responde a la pregunta del OP
Stewart_R

Esto es realmente cómo hacer que una variable de solo lectura sea más que una variable privada. Una variable privada no debe ser accesible al exterior. console.log(instance.property)debe arrojar o darle indefinido, no devolverle "prueba".
oooyaya

3

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

2

La mayoría de las respuestas dicen que es imposible o requieren que use un WeakMap o Symbol, que son características de ES6 que probablemente requerirían polyfills. Sin embargo, hay otra manera! Mira 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

Llamo a este método 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 . Con 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 ningún lado, pero creo que es realmente poderoso.


La pregunta era sobre cómo lograr esto en las clases de ES6.
Michael Franzl

Puede usar exactamente el mismo método en las clases de ES6. Las clases de ES6 son principalmente azúcar además de las funciones que presenté en mi ejemplo. Es muy posible que el póster original esté usando un transpilador, en cuyo caso WeakMaps o Symbols aún requerirán polyfills. Mi respuesta es válida independientemente.
guitarino

2

Vea esta respuesta para obtener una solución limpia y simple de 'clase' con una interfaz pública y privada y soporte para composición


2

Encontré una solución muy simple, solo utilícela Object.freeze(). Por supuesto, el problema es que no puede agregar nada al objeto más 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

esto también deshabilitará el método de setName(name) { this.name = name; }
configuración

2

Yo uso 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


2

En realidad es posible.
1. Primero, cree la clase y en el constructor devuelva la _publicfunción llamada .
2. En la _publicfunción llamada , pase la thisreferencia (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 _publicalcance de la función también está la Namesclase con acceso a this(_esto ) referencia de la Namesclase 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);
}

2

Puedes probar esto https://www.npmjs.com/package/private-members

Este paquete guardará 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;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.