Propiedades privadas en clases de JavaScript ES6

Resuelto d13 asked hace 10 años • 41 respuestas

¿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"
d13 avatar Mar 04 '14 03:03 d13
Aceptado

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
Alister avatar Sep 08 '2018 18:09 Alister

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; }
}
MetalGodwin avatar Jan 27 '2015 07:01 MetalGodwin

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 WeakMapobjeto global. Dicho esto, todo JavaScript puede romperse con globales destrozados.

(Este método también se puede hacer con Map, pero WeakMapes mejor porque Mapcreará 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.definePropertyno 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
twhb avatar Nov 04 '2015 22:11 twhb