Solución:
Respuesta corta, no, no hay soporte nativo para propiedades privadas con clases ES6.
Pero puede imitar ese comportamiento no adjuntando las nuevas propiedades al objeto, sino manteniéndolas dentro de un constructor de clase y usando getters y setters para alcanzar las propiedades ocultas. Tenga en cuenta que los captadores y definidores 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; }
}
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 ámbito
El enfoque aquí es usar 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
WeakMap con alcance
Se puede utilizar un WeakMap para evitar el rendimiento y la penalización de memoria del enfoque anterior. WeakMaps asocia datos con Objetos (aquí, instancias) de tal manera que solo se puede acceder a ellos usando ese WeakMap. Entonces, usamos el método de variables de ámbito para crear un WeakMap privado, luego usamos ese WeakMap para recuperar datos privados asociados con this
. Esto es más rápido que el método de variables de á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 puede usar múltiples WeakMaps y usarlos como age.set(this, 20)
, o escriba un envoltorio pequeño y utilícelo de otra manera, como privateProps.set(this, 'age', 0)
.
Teóricamente, la privacidad de este enfoque podría violarse manipulando el entorno global. WeakMap
objeto. Dicho esto, todo JavaScript puede romperse mediante globales destrozados. Nuestro código ya se basa en la suposición de que esto no está sucediendo.
(Este método también se puede hacer con Map
, pero WeakMap
es mejor porque Map
creará pérdidas de memoria a menos que tenga mucho cuidado, y para este propósito, los dos no son diferentes).
Media respuesta: símbolos con alcance
Un símbolo es un tipo de valor primitivo que puede servir como nombre de propiedad. Puede utilizar el método de variable de ámbito para crear un símbolo privado y luego almacenar datos privados en this[mySymbol]
.
La privacidad de este método se puede violar utilizando 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: subrayados
El antiguo valor predeterminado, simplemente use una propiedad pública con un prefijo de subrayado. Aunque no es una propiedad privada de ninguna manera, esta convención prevalece lo suficiente 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 existe una forma perfecta de hacer propiedades privadas. Varios enfoques tienen pros y contras. Las variables de ámbito son verdaderamente privadas; 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.
Los campos privados (y métodos) se están implementando en el estándar ECMA. Puede comenzar a usarlos hoy con babel 7 y stage 3 preestablecidos.
class Something {
#property;
constructor(){
this.#property = "test";
}
#privateMethod() {
return 'hello world';
}
getPrivateMessage() {
return this.#property;
}
}
const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> test