¿Declarando constantes estáticas en las clases de ES6?

312

Quiero implementar constantes en un class , porque ahí es donde tiene sentido ubicarlas en el código.

Hasta ahora, he estado implementando la siguiente solución con métodos estáticos:

class MyClass {
    static constant1() { return 33; }
    static constant2() { return 2; }
    // ...
}

Sé que existe la posibilidad de jugar con los prototipos, pero muchos recomiendan no hacerlo.

¿Hay una mejor manera de implementar constantes en las clases de ES6?

Jérôme Verstrynge
fuente
77
Personalmente, solo uso VARNAMES en mayúsculas, y me digo que no los toque;)
vecesjr
3
@twicejr Creo que esto no es lo mismo, ya que se puede acceder a las variables estáticas sin primero instanciar un objeto de esa clase.
Lucas Morgan

Respuestas:

386

Aquí hay algunas cosas que puedes hacer:

Exportar a constdesde el módulo . Dependiendo de su caso de uso, podría simplemente:

export const constant1 = 33;

E importe eso desde el módulo cuando sea necesario. O, basándose en su idea del método estático, puede declarar un static get accessor :

const constant1 = 33,
      constant2 = 2;
class Example {

  static get constant1() {
    return constant1;
  }

  static get constant2() {
    return constant2;
  }
}

De esa manera, no necesitará paréntesis:

const one = Example.constant1;

Ejemplo REPL de Babel

Luego, como usted dice, dado que a classes solo azúcar sintáctico para una función, puede agregar una propiedad no escribible de esta manera:

class Example {
}
Object.defineProperty(Example, 'constant1', {
    value: 33,
    writable : false,
    enumerable : true,
    configurable : false
});
Example.constant1; // 33
Example.constant1 = 15; // TypeError

Puede ser bueno si pudiéramos hacer algo como:

class Example {
    static const constant1 = 33;
}

Pero desafortunadamente, esta sintaxis de propiedad de clase solo está en una propuesta de ES7, e incluso entonces no permitirá agregarla consta la propiedad.

CodificaciónIntriga
fuente
¿Hay alguna confirmación de que las propiedades estáticas se calculan una vez para cosas como esta, o es más seguro usar IIFE y agregar la propiedad manualmente en el IIFE para evitar la construcción repetida de valores de retorno? Me preocupa que si el resultado del captador es realmente pesado, como un objeto JSO de entrada de 100.000, entonces el captador pobre tendrá que construirlo cada vez que se llame al captador. Es fácil de probar con performance.now/date diff, pero podría implementarse de manera diferente, es mucho más fácil implementar getters como evaluación literal en lugar de decisiones avanzadas, ya sea constante o no.
Dmitry
3
mientras que lo anterior agrega inteligentemente una propiedad constante a una clase, el valor real de la constante está "fuera" de la definición de clase "{}", lo que realmente viola una de las definiciones de encapsulación. Supongo que es suficiente simplemente definir una propiedad constante "dentro" de la clase y no es necesario obtenerla en este caso.
NoChance
1
@NoChance Buenos puntos. Eso fue solo ilustrativo. No hay razón para que el método getter no pueda encapsular completamente el valor si es necesario.
CodingIntrigue
Espero utilizar la propuesta ES7 porque me parece más natural y equivalente a la mayoría de los idiomas OO.
Sangimed
¿Qué quiero declarar constante una variable de instancia? ¿Puedo hacer algo asíthis.defineProperty(this, 'constant1', {...})
Francesco Boi
33
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

Parece funcionar para mi.

Benny Jobigan
fuente
¿Es esto accesible dentro de la clase en un método normal?
PirateApp
3
@PirateApp puede acceder a él desde cualquier lugar como método estático, incluso desde una instancia de la clase. Sin embargo, dado que es estático, no se puede usar this.MyConstdesde una Whateverinstancia, siempre se debe escribir así: Whatever.MyConst
TheDarkIn1978 el
23

Estoy usando babely la siguiente sintaxis está funcionando para mí:

class MyClass {
    static constant1 = 33;
    static constant2 = {
       case1: 1,
       case2: 2,
    };
    // ...
}

MyClass.constant1 === 33
MyClass.constant2.case1 === 1

Tenga en cuenta que necesita el preajuste "stage-0".
Para instalarlo:

npm install --save-dev babel-preset-stage-0

// in .babelrc
{
    "presets": ["stage-0"]
}

Actualizar:

uso actual stage-3

borracciaBlu
fuente
21
El problema es que la constante es reasignable. Op no quiere eso
CodingIntrigue
3
FYI, esto ahora está en babelstage-2
bmaupin
3
esas no son constantes
Dave L.
1
@CodingIntrigue ¿Llamar Object.freeze()a la clase solucionaría eso?
Antimonio
1
@Antimony No lo he probado pero creo que sí. El problema es que se aplicaría a todas las propiedades de la clase. No estático también.
CodingIntrigue
14

En este documento dice:

No existe (intencionalmente) una forma declarativa directa de definir propiedades de datos de prototipo (que no sean métodos) propiedades de clase o propiedad de instancia

Esto significa que es intencionalmente así.

¿Quizás puedas definir una variable en el constructor?

constructor(){
    this.key = value
}
DevAlien
fuente
2
Sí, esto puede funcionar. Además, quiero mencionar que ese constructor invoca cuando se crea la instancia y para cada instancia this.key no será la misma. El método estático y las propiedades nos permiten usarlos directamente desde la clase, sin crear una instancia. Hay puntos buenos y débiles de métodos / propiedades estáticos.
Kirill Gusyatin
1
Las constantes deben ser inmutables. Asignar a propiedades en el objeto durante la construcción producirá propiedades que se pueden modificar.
philraj
11

También es posible usar Object.freezeen su objeto de clase (es6) / función de constructor (es5) para hacerlo inmutable:

class MyConstants {}
MyConstants.staticValue = 3;
MyConstants.staticMethod = function() {
  return 4;
}
Object.freeze(MyConstants);
// after the freeze, any attempts of altering the MyConstants class will have no result
// (either trying to alter, add or delete a property)
MyConstants.staticValue === 3; // true
MyConstants.staticValue = 55; // will have no effect
MyConstants.staticValue === 3; // true

MyConstants.otherStaticValue = "other" // will have no effect
MyConstants.otherStaticValue === undefined // true

delete MyConstants.staticMethod // false
typeof(MyConstants.staticMethod) === "function" // true

Intentar alterar la clase te dará una falla suave (no arrojará ningún error, simplemente no tendrá ningún efecto).

rodrigo.botti
fuente
3
Ese fallo suave es bastante aterrador para aquellos de nosotros que venimos de otros idiomas, simplemente adaptándonos a la idea de que las herramientas no nos ayudan mucho a encontrar errores, ahora incluso el tiempo de ejecución no ayudará. (De lo contrario, me gusta su solución.)
Tom
Me encanta Object.freeze()forzar la inmutabilidad, y la he estado usando mucho últimamente. ¡Solo no olvides aplicarlo recursivamente!
jeffwtribble
6

¿Quizás solo ponga todas sus constantes en un objeto congelado?

class MyClass {

    constructor() {
        this.constants = Object.freeze({
            constant1: 33,
            constant2: 2,
        });
    }

    static get constant1() {
        return this.constants.constant1;
    }

    doThisAndThat() {
        //...
        let value = this.constants.constant2;
        //...
    }
}
aRIEL
fuente
La función estática no puede usar la variable 'this'.
PokerFace
4

Como dijo https://stackoverflow.com/users/2784136/rodrigo-botti , creo que estás buscando Object.freeze(). Aquí hay un ejemplo de una clase con estática inmutable:

class User {
  constructor(username, age) {
    if (age < User.minimumAge) {
      throw new Error('You are too young to be here!');
    }
    this.username = username;
    this.age = age;
    this.state = 'active';
  }
}

User.minimumAge = 16;
User.validStates = ['active', 'inactive', 'archived'];

deepFreeze(User);

function deepFreeze(value) {
  if (typeof value === 'object' && value !== null) {
    Object.freeze(value);
    Object.getOwnPropertyNames(value).forEach(property => {
      deepFreeze(value[property]);
    });
  }
  return value;
}
jeffwtribble
fuente
1

Aquí hay una manera más que puedes hacer

/*
one more way of declaring constants in a class,
Note - the constants have to be declared after the class is defined
*/
class Auto{
   //other methods
}
Auto.CONSTANT1 = "const1";
Auto.CONSTANT2 = "const2";

console.log(Auto.CONSTANT1)
console.log(Auto.CONSTANT2);

Nota: el orden es importante, no puede tener las constantes anteriores

Uso console.log (Auto.CONSTANT1);

usuario3871424
fuente
55
Sin embargo
John Harding
1

Puede crear una forma de definir constantes estáticas en una clase utilizando una característica extraña de las clases ES6. Dado que las subclases heredan las estadísticas, puede hacer lo siguiente:

const withConsts = (map, BaseClass = Object) => {
  class ConstClass extends BaseClass { }
  Object.keys(map).forEach(key => {
    Object.defineProperty(ConstClass, key, {
      value: map[key],
      writable : false,
      enumerable : true,
      configurable : false
    });
  });
  return ConstClass;
};

class MyClass extends withConsts({ MY_CONST: 'this is defined' }) {
  foo() {
    console.log(MyClass.MY_CONST);
  }
}
TbWill4321
fuente
1

Puede hacer que las "constantes" sean de solo lectura (inmutables) congelando la clase. p.ej

class Foo {
    static BAR = "bat"; //public static read-only
}

Object.freeze(Foo); 

/*
Uncaught TypeError: Cannot assign to read only property 'BAR' of function 'class Foo {
    static BAR = "bat"; //public static read-only
}'
*/
Foo.BAR = "wut";
Fraser
fuente
0

Si se siente cómodo mezclando y haciendo coincidir la función y la sintaxis de clase, puede declarar constantes después de la clase (las constantes se 'levantan'). Tenga en cuenta que Visual Studio Code tendrá dificultades para formatear automáticamente la sintaxis mixta (aunque funciona).

class MyClass {
    // ...

}
MyClass.prototype.consts = { 
    constant1:  33,
    constant2: 32
};
mc = new MyClass();
console.log(mc.consts.constant2);    

Cairns de levas
fuente
0

Hice esto.

class Circle
{
    constuctor(radius)
    {
        this.radius = radius;
    }
    static get PI()
    {
        return 3.14159;
    }
}

El valor de PI está protegido contra cambios ya que es un valor que se devuelve desde una función. Puede acceder a través de Circle.PI. Cualquier intento de asignarlo simplemente se deja caer al suelo de una manera similar a un intento de asignar un carácter de cadena a través de [].

ncmathsadist
fuente
0

Puedes definirlo así:

class Foo {
  static MyConst = 200;

  myFunc() {
    const doubleConst = Foo.MyConst * 2;
  }
}
zmechanic
fuente
0

Podrías usar la import * assintaxis. Aunque no es una clase, son constvariables reales .

Constantes.js

export const factor = 3;
export const pi = 3.141592;

index.js

import * as Constants from 'Constants.js'
console.log( Constants.factor );
Vincent
fuente