Propiedades privadas en clases de JavaScript ES6
¿Es posible crear propiedades privadas en clases de ES6?
He aquí un ejemplo. ¿ Cómo puedo impedir el acceso a instance.property
?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
Las funciones de clase privada ahora son compatibles con la mayoría de los navegadores.
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
console.log(instance.#property); //=> Syntax error
Actualización: vea la respuesta de otros, esto está desactualizado.
Respuesta corta: no, no existe soporte nativo para propiedades privadas con clases ES6.
Pero podría imitar ese comportamiento no adjuntando las nuevas propiedades al objeto, sino manteniéndolas dentro de un constructor de clase y usando captadores y definidores 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; }
}
Sí, anteponga el nombre #
e inclúyalo en la definición de clase, no solo en el constructor.
Documentos de MDN
Finalmente se agregaron propiedades privadas reales en ES2022. A partir del 1 de enero de 2023, las propiedades privadas (campos y métodos) han sido compatibles con todos los navegadores principales durante al menos un año, pero entre el 5 y el 10 % de los usuarios todavía utilizan navegadores más antiguos [¿ Puedo usar? ].
Ejemplo:
class Person {
#age
constructor(name) {
this.name = name; // this is public
this.#age = 20; // this is 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 name but not age
A continuación se presentan métodos para mantener las propiedades privadas en entornos anteriores a ES2022, con varias compensaciones.
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 recreará con cada instancia. Esto supone una penalización de rendimiento y memoria, pero puede ser aceptable. La penalización se puede evitar para los métodos que no necesitan acceso a datos privados declarándolos de la forma habitual.
Ejemplo:
class Person {
constructor(name) {
let age = 20; // this is private
this.name = name; // this is public
this.greet = () => {
// here we can access both name and age
console.log(`name: ${this.name}, age: ${age}`);
};
}
anotherMethod() {
// here we can access name but not age
}
}
let joe = new Person('Joe');
joe.greet();
// here we can access name but not age
Mapa débil con alcance
Se puede utilizar un WeakMap para mejorar el rendimiento del enfoque anterior, a cambio de aún más desorden. WeakMaps asocia datos con Objetos (aquí, instancias de clase) de tal manera que solo se puede acceder a ellos utilizando ese WeakMap. Entonces, usamos el método de variables de ámbito para crear un WeakMap privado y luego usamos ese WeakMap para recuperar datos privados asociados con this
. Esto es más rápido que el método de variables de alcance porque todas sus instancias pueden compartir un único WeakMap, por lo que no necesita recrear métodos solo para que accedan a sus propios WeakMaps.
Ejemplo:
let Person = (function () {
let privateProps = new WeakMap();
return 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}`);
}
};
})();
let joe = new Person('Joe');
joe.greet();
// here we can access name but not age
Este ejemplo utiliza un WeakMap con claves de objeto para usar un WeakMap para múltiples propiedades privadas; También puedes usar múltiples WeakMaps y usarlos como privateAge.set(this, 20)
, o escribir un pequeño contenedor y usarlo de otra manera, como privateProps.set(this, 'age', 0)
.
En teoría, la privacidad de este enfoque podría violarse al manipular el WeakMap
objeto global. Dicho esto, todo JavaScript puede romperse con globales destrozados.
(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 en lugar de una cadena. 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 usando Object.getOwnPropertySymbols
, pero es algo incómodo de hacer.
Ejemplo:
let Person = (() => {
let ageKey = Symbol();
return 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]}`);
}
}
})();
let joe = new Person('Joe');
joe.greet();
// Here we can access joe's name and, with a little effort, age. We can’t
// access ageKey directly, but we can obtain it by listing all Symbol
// properties on `joe` with `Object.getOwnPropertySymbols(joe)`.
Tenga en cuenta que hacer que una propiedad no sea enumerable usando Object.defineProperty
no impide que se incluya en Object.getOwnPropertySymbols
.
Media respuesta: guiones bajos
La antigua convención es utilizar simplemente una propiedad pública con un prefijo de guión bajo. Esto no lo mantiene privado, pero hace un buen trabajo al comunicar a los lectores que deben tratarlo como privado, lo que a menudo hace el trabajo. A cambio de esto, obtenemos un enfoque que es más fácil de leer, más fácil de escribir y más rápido que otras soluciones alternativas.
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.
Resumen
- ES2022: excelente, pero aún no es compatible con todos los visitantes
- Variables de alcance: privadas, más lentas, incómodas
- Scoped WeakMaps: pirateable, incómodo
- Símbolos con alcance: enumerables y pirateables, algo incómodos
- Guiones bajos: solo una solicitud de privacidad, sin otras desventajas