Declarando constantes estáticas nas classes ES6?

312

Eu quero implementar constantes em a class, porque é aí que faz sentido localizá-las no código.

Até agora, tenho implementado a seguinte solução alternativa com métodos estáticos:

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

Eu sei que há uma possibilidade de mexer com protótipos, mas muitos recomendam isso.

Existe uma maneira melhor de implementar constantes nas classes ES6?

Jérôme Verstrynge
fonte
7
Pessoalmente eu só uso maiúscula nome de variáveis, e digo a mim mesmo para não tocá-los;)
twicejr
3
@twicejr Eu acho que isso não é o mesmo, pois variáveis ​​estáticas podem ser acessadas sem antes instanciar um objeto dessa classe?
Lucas Morgan

Respostas:

386

Aqui estão algumas coisas que você pode fazer:

Exporte um constdo módulo . Dependendo do seu caso de uso, você pode apenas:

export const constant1 = 33;

E importe isso do módulo sempre que necessário. Ou, desenvolvendo sua ideia de método estático, você pode declarar um static acessador get :

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

  static get constant1() {
    return constant1;
  }

  static get constant2() {
    return constant2;
  }
}

Dessa forma, você não precisará de parênteses:

const one = Example.constant1;

Exemplo de Babel REPL

Então, como você diz, como a classé apenas um açúcar sintático para uma função, você pode adicionar uma propriedade não gravável da seguinte forma:

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

Pode ser bom se pudéssemos fazer algo como:

class Example {
    static const constant1 = 33;
}

Infelizmente, porém, essa sintaxe de propriedade de classe está apenas em uma proposta do ES7 e, mesmo assim, não permitirá a adição constà propriedade.

CodingIntrigue
fonte
existe alguma confirmação de que as propriedades estáticas são computadas uma vez para coisas como essa ou é mais seguro usar o IIFE e adicionar a propriedade manualmente no IIFE para evitar a construção repetida dos valores de retorno. Estou preocupado que, se o resultado do getter for realmente pesado, como um JSObject de 100.000 entradas, o getter pobre precisará construí-lo toda vez que o getter for chamado. É fácil de testar pelo desempenho.now/date diff, mas pode ser implementado de maneira diferente; é certamente mais fácil implementar getters como avaliação literal do que como decisões avançadas, sejam constantes ou não.
Dmitry
3
enquanto o acima habilmente adiciona uma propriedade constante a uma classe, o valor real da constante é "fora" da definição de classe "{}", o que realmente viola uma das definições de encapsulamento. Eu acho que é suficiente apenas definir uma propriedade constante "dentro" da classe e não há necessidade de obter neste caso.
precisa saber é o seguinte
1
@NoChance Bons pontos. Isso foi apenas ilustrativo. Não há razão para o método getter não conseguir encapsular completamente o valor, se necessário.
CodingIntrigue 5/17
Ansioso para usar a proposta ES7, porque me parece mais natural e equivalente à maioria das línguas OO.
precisa saber é o seguinte
O que eu quero declarar constante uma variável de instância? Posso fazer algo parecido comthis.defineProperty(this, 'constant1', {...})
Francesco Boi
33
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

Parece funcionar para mim.

Benny Jobigan
fonte
isso é acessível dentro da classe em um método normal?
PirateApp 21/04/19
3
@PirateApp, você pode acessá-lo em qualquer lugar como um método estático, mesmo de dentro de uma instância da classe. No entanto, desde que é estático, você não pode usar this.MyConstde dentro de um Whateverexemplo, você sempre tem que escrevê-lo como este: Whatever.MyConst
TheDarkIn1978
23

Estou usando babele a seguinte sintaxe está funcionando para mim:

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

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

Por favor, considere que você precisa da predefinição "stage-0".
Para instalá-lo:

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

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

Atualizar:

atualmente usa stage-3

borracciaBlu
fonte
21
O problema é que constante é reatribuível. O Op não quer isso
CodingIntrigue
3
FYI, este está agora em babelstage-2
bmaupin
3
aqueles que não são constantes
Dave L.
1
@CodingIntrigue Será que chamar Object.freeze()a classe corrige isso?
Antimony
1
@ Antimonony Eu não testei isso, mas acho que sim. O problema é que isso se aplicaria a todas as propriedades da classe. Não estático também.
CodingIntrigue 12/09
14

No presente documento ele afirma:

Não há (intencionalmente) nenhuma maneira declarativa direta de definir propriedades de classe de protótipo de dados (exceto métodos) ou propriedade de instância

Isso significa que é intencionalmente assim.

Talvez você possa definir uma variável no construtor?

constructor(){
    this.key = value
}
DevAlien
fonte
2
Sim, isso pode funcionar. Além disso, quero mencionar que o construtor chama quando a instância é criada e para cada instância this.key não será a mesma. Método e propriedades estáticas nos permitem usá-los diretamente da classe, sem criar instância. Existem pontos bons e fracos de métodos / propriedades estáticas.
Kirill Gusyatin
1
As constantes devem ser imutáveis. A atribuição de propriedades ao objeto durante a construção produzirá propriedades que podem ser modificadas.
philraj
11

Também é possível usar Object.freezeem seu objeto de classe (es6) / função construtora (es5) para torná-lo imutável:

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

Tentar alterar a classe resultará em uma falha leve (não causará erros, simplesmente não terá efeito).

rodrigo.botti
fonte
3
Essa falha suave é bastante assustadora para aqueles que vêm de outros idiomas - apenas adaptando-se à idéia de que as ferramentas não nos ajudam muito a encontrar erros, agora mesmo o tempo de execução não ajuda. (Caso contrário, eu gosto de sua solução.)
Tom
Adoro Object.freeze()impor a imutabilidade e tenho usado muito ultimamente. Só não se esqueça de aplicá-lo recursivamente!
jeffwtribble
6

Talvez apenas coloque todas as suas constantes em um 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
fonte
A função estática não pode usar a variável 'this'.
PokerFace
4

Como https://stackoverflow.com/users/2784136/rodrigo-botti disse, acho que você está procurando Object.freeze(). Aqui está um exemplo de uma classe com estática imutável:

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
fonte
1

Aqui está mais uma maneira de você fazer

/*
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 - a ordem é importante, você não pode ter as constantes acima

Uso console.log (Auto.CONSTANT1);

user3871424
fonte
5
Eles não são imutáveis embora
John Harding
1

Você pode criar uma maneira de definir constantes estáticas em uma classe usando um recurso estranho das classes ES6. Como as estáticas são herdadas por suas subclasses, você pode fazer o seguinte:

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
fonte
1

Você pode tornar as "constantes" somente leitura (imutáveis) congelando a classe. por exemplo

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
fonte
0

Se você se sente à vontade para misturar e combinar sintaxe de função e classe, pode declarar constantes após a classe (as constantes são 'levantadas'). Observe que o Visual Studio Code terá dificuldade para formatar automaticamente a sintaxe mista (embora funcione).

class MyClass {
    // ...

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

Cam Cairns
fonte
0

Eu fiz isso.

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

O valor do PI está protegido contra alterações, pois é um valor retornado de uma função. Você pode acessá-lo via Circle.PI. Qualquer tentativa de atribuir a ele é simplesmente descartada no chão de maneira semelhante a uma tentativa de atribuir a um caractere de string via [].

ncmathsadist
fonte
0

Você pode defini-lo assim:

class Foo {
  static MyConst = 200;

  myFunc() {
    const doubleConst = Foo.MyConst * 2;
  }
}
zmecânico
fonte
0

Você pode usar import * assintaxe. Embora não sejam uma classe, são constvariáveis reais .

Constants.js

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

index.js

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