Déclarer des constantes statiques dans les classes ES6?

312

Je veux implémenter des constantes dans a class, car c'est là qu'il est logique de les localiser dans le code.

Jusqu'à présent, j'ai implémenté la solution de contournement suivante avec des méthodes statiques:

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

Je sais qu'il est possible de jouer avec les prototypes, mais beaucoup le déconseillent.

Existe-t-il un meilleur moyen d'implémenter des constantes dans les classes ES6?

Jérôme Verstrynge
la source
7
Personnellement , je viens d' utiliser les majuscules noms de variables, et je me dis de ne pas les toucher;)
twicejr
3
@twicejr Je pense que ce n'est pas la même chose, car les variables statiques sont accessibles sans avoir d'abord instancié un objet de cette classe?
Lucas Morgan

Réponses:

386

Voici quelques choses que vous pourriez faire:

Exportez un constdepuis le module . Selon votre cas d'utilisation, vous pouvez simplement:

export const constant1 = 33;

Et importez cela depuis le module si nécessaire. Ou, en vous appuyant sur votre idée de méthode statique, vous pouvez déclarer un static accesseur get :

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

  static get constant1() {
    return constant1;
  }

  static get constant2() {
    return constant2;
  }
}

De cette façon, vous n'aurez pas besoin de parenthèses:

const one = Example.constant1;

Exemple Babel REPL

Ensuite, comme vous le dites, comme a classn'est qu'un sucre syntaxique pour une fonction, vous pouvez simplement ajouter une propriété non inscriptible comme ceci:

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

Ce serait bien si nous pouvions faire quelque chose comme:

class Example {
    static const constant1 = 33;
}

Mais malheureusement, cette syntaxe de propriété de classe est uniquement dans une proposition ES7, et même alors, elle ne permettra pas d'ajouter constà la propriété.

CodageIntrigue
la source
existe-t-il une confirmation que les propriétés statiques sont calculées une fois pour des choses comme celle-ci, ou est-il plus sûr d'utiliser IIFE et d'ajouter la propriété manuellement dans IIFE pour éviter la construction répétée de valeurs de retour. Je crains que si le résultat du getter est vraiment lourd, comme un JSObject à 100 000 entrées, le pauvre getter devra le construire à chaque appel du getter. Il est facile de tester par diff performance.now/date, mais il peut être implémenté différemment, il est certes plus facile d'implémenter des getters comme une évaluation littérale plutôt que des décisions avancées, que ce soit constant ou non.
Dmitry
3
tandis que ce qui précède ajoute intelligemment une propriété constante à une classe, la valeur réelle de la constante est "en dehors" de la définition de classe "{}", ce qui viole vraiment l'une des définitions de l'encapsulation. Je suppose qu'il suffit de définir une propriété constante "à l'intérieur" de la classe et il n'y a pas besoin de get dans ce cas.
NoChance
1
@NoChance Bons points. C'était juste illustratif. Il n'y a aucune raison que la méthode getter ne puisse pas encapsuler complètement la valeur si nécessaire.
CodingIntrigue
J'ai hâte d'utiliser la proposition ES7 car elle me semble plus naturelle et équivalente à la majorité des langues OO.
Sangimed
Qu'est-ce que je veux déclarer constante une variable d'instance? Puis-je faire quelque chose commethis.defineProperty(this, 'constant1', {...})
Francesco Boi
33
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

Semble fonctionner pour moi.

Benny Jobigan
la source
est-ce accessible à l'intérieur de la classe dans une méthode normale?
PirateApp
3
@PirateApp vous pouvez y accéder n'importe où en tant que méthode statique, même depuis l'intérieur d'une instance de la classe. Cependant, comme il est statique que vous ne pouvez pas utiliser this.MyConstà l'intérieur d'une Whateverinstance, vous devez toujours l'écrire comme ceci: Whatever.MyConst
TheDarkIn1978
23

J'utilise babelet la syntaxe suivante fonctionne pour moi:

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

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

Veuillez considérer que vous avez besoin du préréglage "stage-0".
Pour l'installer:

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

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

Mettre à jour:

utiliser actuellement stage-3

borracciaBlu
la source
21
Le problème est que la constante est réaffectable. Op ne veut pas ça
CodingIntrigue
3
Pour info, c'est maintenant à babelstage-2
bmaupin
3
ce ne sont pas des constantes
Dave L.
1
@CodingIntrigue L'appel Object.freeze()à la classe résoudrait-il cela?
Antimony
1
@Antimony Je n'ai pas testé cela mais je pense que oui. Le problème est qu'il s'appliquerait à toutes les propriétés de la classe. Non statique également.
CodingIntrigue
14

Dans ce document, il indique:

Il n'y a (intentionnellement) aucun moyen déclaratif direct de définir les propriétés de classe des propriétés de données du prototype (autres que les méthodes) ou la propriété d'instance

Cela signifie que c'est intentionnellement comme ça.

Peut-être pouvez-vous définir une variable dans le constructeur?

constructor(){
    this.key = value
}
DevAlien
la source
2
Oui, cela peut fonctionner. En outre, je tiens à mentionner que le constructeur invoque lors de la création de l'instance et que pour chaque instance this.key ne sera pas le même. La méthode et les propriétés statiques nous permettent de les utiliser directement depuis la classe, sans créer d'instance. Il existe de bons et de faibles points pour les méthodes / propriétés statiques.
Kirill Gusyatin
1
Les constantes doivent être immuables. L'affectation à des propriétés sur l'objet pendant la construction produira des propriétés qui peuvent être modifiées.
philraj
11

Il est également possible d'utiliser Object.freezesur votre objet classe (es6) / fonction constructeur (es5) pour le rendre immuable:

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

Essayer de modifier la classe vous donnera un soft-fail (ne lancera aucune erreur, cela n'aura tout simplement aucun effet).

rodrigo.botti
la source
3
Ce soft-fail est assez effrayant pour ceux d'entre nous venant d'autres langues - il suffit de s'adapter à l'idée que les outils ne nous aident pas beaucoup à trouver des erreurs, maintenant même le runtime n'aidera pas. (Sinon, j'aime votre solution.)
Tom
J'adore Object.freeze()imposer l'immuabilité et je l'utilise beaucoup ces derniers temps. N'oubliez pas de l'appliquer récursivement!
jeffwtribble
6

Peut-être juste mettre toutes vos constantes dans un objet figé?

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
la source
La fonction statique ne peut pas utiliser la variable «this».
PokerFace
4

Comme https://stackoverflow.com/users/2784136/rodrigo-botti a dit, je pense que vous cherchez Object.freeze(). Voici un exemple de classe avec une statique immuable:

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
la source
1

Voici une autre façon de faire

/*
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);

Remarque - L'ordre est important, vous ne pouvez pas avoir les constantes ci-dessus

Utilisation console.log (Auto.CONSTANT1);

user3871424
la source
5
Mais ils ne sont pas immuables
John Harding
1

Vous pouvez créer un moyen de définir des constantes statiques sur une classe en utilisant une caractéristique étrange des classes ES6. Étant donné que les statiques sont héritées par leurs sous-classes, vous pouvez effectuer les opérations suivantes:

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
la source
1

Vous pouvez rendre les "constantes" en lecture seule (immuables) en gelant la classe. par exemple

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
la source
0

Si vous êtes à l'aise pour mélanger et faire correspondre la fonction et la syntaxe de classe, vous pouvez déclarer des constantes après la classe (les constantes sont «levées»). Notez que Visual Studio Code aura du mal à formater automatiquement la syntaxe mixte (bien que cela fonctionne).

class MyClass {
    // ...

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

Cam Cairns
la source
0

J'ai fait ça.

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

La valeur de PI est protégée contre toute modification car il s'agit d'une valeur renvoyée par une fonction. Vous pouvez y accéder via Circle.PI. Toute tentative de l'assigner est simplement abandonnée sur le sol d'une manière similaire à une tentative d'assigner à un caractère de chaîne via [].

ncmathsadist
la source
0

Vous pouvez le définir comme ceci:

class Foo {
  static MyConst = 200;

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

Vous pouvez utiliser la import * assyntaxe. Bien qu'il ne s'agisse pas d'une classe, ce sont de vraies constvariables.

Constants.js

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

index.js

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