Comment créer un «champ statique public» dans une classe ES6?

86

Je crée une classe Javascript et j'aimerais avoir un champ statique public comme en Java. Voici le code pertinent:

export default class Agent {
    CIRCLE: 1,
    SQUARE: 2,
    ...

Voici l'erreur que j'obtiens:

line 2, col 11, Class properties must be methods. Expected '(' but instead saw ':'.

Il semble que les modules ES6 ne permettent pas cela. Existe-t-il un moyen d'obtenir le comportement souhaité ou dois-je écrire un getter?

aebabis
la source
Quelle implémentation du moteur ECMAScript 6 utilisez-vous?
Dai

Réponses:

136

Vous créez un "champ statique public" en utilisant l'accesseur et un mot clé "statique":

class Agent {
    static get CIRCLE() {
      return 1;
    }
    static get SQUARE() {
      return 2;
    }
}

Agent.CIRCLE; // 1

En regardant une spécification, 14.5 - Définitions de classe - vous verriez quelque chose de suspectement pertinent :)

ClassElement [Rendement]:
  MethodDefinition [? Rendement]
  statique MethodDefinition [? Rendement];

Ainsi, à partir de là, vous pouvez suivre la 14.5.14 - Sémantique d'exécution: ClassDefinitionEvaluation - pour vérifier si elle fait vraiment ce à quoi elle ressemble. Plus précisément, étape 20:

  1. Pour chaque ClassElement m dans l'ordre des méthodes
    1. Si IsStatic de m est faux , alors
      1. Soit status le résultat de l'exécution de PropertyDefinitionEvaluation pour m avec les arguments proto et false.
    2. Autre,
      1. Soit status le résultat de l'exécution de PropertyDefinitionEvaluation pour m avec les arguments F et false.
    3. Si l'état est un achèvement brutal, alors
      1. Définissez lexicalEnvironment du contexte d'exécution d'exécution sur lex.
      2. Statut de retour.

IsStatic est défini précédemment dans 14.5.9

ClassElement: statique MethodDefinition
Renvoie true.

Ainsi PropertyMethodDefinitionest appelé avec "F" (constructeur, objet fonction) comme argument, qui à son tour crée une méthode accesseur sur cet objet .

Cela fonctionne déjà au moins dans IETP (aperçu technique), ainsi que dans les compilateurs 6to5 et Traceur.

Kangax
la source
Pour tous ceux qui recherchent, les propriétés de l'accesseur statique ne sont pas encore prises en charge dans Node. : - / kangax.github.io/compat-table/es6/…
David Hernandez
1
Depuis au moins Node.js 6.x +, cela est pris en charge.
NuSkooler
Notez que si vous utilisez flow, vous devez ajouter une ligne unsafe.enable_getters_and_setters=trueà votre .flowconfig sous [options](ce qui est ennuyeux).
kristina
Cela ne fonctionnera pas pour moi.Je reçois `` `` Unhandled rejection TypeError: Cannot set property dataHashKey of class Collections {api_1 | statique get dataHashKey () {api_1 | retourner 'collections'; api_1 | } `` ``
Pavan
54

Il existe une proposition ECMAScript Stage 3 appelée "Static Class Features" par Daniel Ehrenberg et Jeff Morrison qui vise à résoudre ce problème. Avec la proposition "Champs de classe" de l'étape 3 , le futur code ressemblera à ceci:

class MyClass {
    static myStaticProp = 42;
    myProp = 42;
    myProp2 = this.myProp;
    myBoundFunc = () => { console.log(this.myProp); };

    constructor() {
        console.log(MyClass.myStaticProp); // Prints '42'
        console.log(this.myProp); // Prints '42'
        this.myBoundFunc(); // Prints '42'
    }
}

Ce qui précède est équivalent à:

class MyClass {
    constructor() {
        this.myProp = 42;
        this.myProp2 = this.myProp;
        this.myBoundFunc = () => { console.log(this.myProp); };

        console.log(MyClass.myStaticProp); // Prints '42'
        console.log(this.myProp); // Prints '42'
        this.myBoundFunc(); // Prints '42'
    }
}
MyClass.myStaticProp = 42;

Babel prend en charge la transpilation des champs de classe via @ babel / plugin-proposal-class-properties (inclus dans le préréglage stage-3 ), afin que vous puissiez utiliser cette fonctionnalité même si votre environnement d'exécution JavaScript ne la prend pas en charge.


Comparée à la solution de @ kangax de déclarer un getter, cette solution peut également être plus performante, car ici la propriété est accédée directement au lieu d'appeler une fonction.

Si cette proposition est acceptée, il sera alors possible d'écrire du code JavaScript d'une manière plus similaire aux langages orientés objet traditionnels tels que Java et C♯.


Edit : Une proposition de champs de classe unifiée est maintenant à l'étape 3; mise à jour vers les packages Babel v7.x.

Edit (février 2020) : Les fonctionnalités de classe statique ont été divisées en une proposition différente. Merci @ GOTO0!

Timothy Gu
la source
Je pense que la proposition pertinente est en fait celle-ci ( caractéristiques de la classe statique ).
GOTO 0
29

Dans les versions actuelles d'ECMAScript 6 (à partir de février 2015), toutes les propriétés de classe doivent être des méthodes et non des valeurs (notez que dans ECMAScript, une "propriété" est similaire dans son concept à un champ POO, sauf que la valeur du champ doit être un Functionobjet, pas une une autre valeur telle que a Numberou Object).

Vous pouvez toujours les spécifier à l'aide des spécificateurs de propriété de constructeur ECMAScript traditionnels:

 class Agent {
 }
 Agent.CIRCLE = 1;
 Agent.SQUARE = 2;
 ...
Dai
la source
11
Notez que la classsyntaxe ES6 n'est de toute façon que du sucre syntaxique pour les fonctions de constructeur et les prototypes JS traditionnels.
Matt Browne
Je pense que vous voudriez mettre ces propriétés sur le prototype et non sur le constructeur pour qu'elles soient visibles via les références de propriété des instances.
Pointy
@Pointy J'ai déduit que l'OP essayait de stocker des constantes pour référence (presque comme un C # /. NET enum).
Dai
2
@MattBrowne Oui, mais pour être claire, la classsyntaxe présente également certaines différences nuancées. Par exemple, une méthode déclarée avec Class.prototype.method = function () {};est énumérable (visible avec des boucles for-in), tandis que les classméthodes ne sont pas énumérables.
Timothy Gu
4

Pour tirer pleinement parti de la variable statique, j'ai suivi cette approche. Pour être plus précis, nous pouvons l'utiliser pour utiliser une variable privée ou avoir seulement un getter public, ou avoir à la fois un getter ou un setter. Dans le dernier cas, c'est la même chose que l'une des solutions affichées ci-dessus.

var Url = (() => {
    let _staticMember = [];
    return class {
        static getQueries(hash = document.location.hash) {
            return hash;
        }

        static get staticMember(){
            return _staticMember;
        }
    };
})();

Usages:
console.log(Url.staticMember); // [];
Url.staticMember.push('it works');
console.log(Url.staticMember); // ['it works'];

Je pourrais créer une autre classe étendant Url et cela a fonctionné.

J'ai utilisé babel pour convertir mon code ES6 en ES5

SM Adnan
la source
1
Qu'est-ce que le «plein avantage»? Cela n'aurait pas class Url { static getQueries… }; Url.staticMember = [];été beaucoup plus simple?
Bergi
Ces ===comparaisons donnent à la fois false, btw
Bergi
"Full Advantage" signifie, de la manière ci-dessus, vous pouvez garder _staticMember comme privé, si vous le souhaitez.
SM Adnan
-1

La réponse de @kangax n'imite pas tout le comportement statique du langage OOP traditionnel, car vous ne pouvez pas accéder à la propriété statique par son instance comme const agent = new Agent; agent.CIRCLE; // Undefined

Si vous souhaitez accéder à une propriété statique comme celle de la POO, voici ma solution:

class NewApp {
  get MULTIPLE_VERSIONS_SUPPORTED() {
    return this.constructor.MULTIPLE_VERSIONS_SUPPORTED; // Late binding for inheritance
  }
}

NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

Testez le code comme suit.

class NewApp {
  get MULTIPLE_VERSIONS_SUPPORTED() {
    console.log('this.constructor.name:', this.constructor.name); // late binding
    return this.constructor.MULTIPLE_VERSIONS_SUPPORTED;
  }
}

// Static property can be accessed by class
NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

const newApp = new NewApp;

// Static property can be accessed by it's instances
console.log('newApp.MULTIPLE_VERSIONS_SUPPORTED:', newApp.MULTIPLE_VERSIONS_SUPPORTED); // true

// Inheritance
class StandardApp extends NewApp {}

// Static property can be inherited
console.log('StandardApp.MULTIPLE_VERSIONS_SUPPORTED:', StandardApp.MULTIPLE_VERSIONS_SUPPORTED); // true

// Static property can be overwritten
StandardApp.MULTIPLE_VERSIONS_SUPPORTED = false;

const std = new StandardApp;

console.log('std.MULTIPLE_VERSIONS_SUPPORTED:', std.MULTIPLE_VERSIONS_SUPPORTED); // false

légende des années 80
la source
1
Accéder à un staticchamp par une instance serait plutôt rare, n'est-ce pas? Dans certains langages, tels que Java, les IDE émettent en fait un avertissement / un indice si vous faites quelque chose comme ça.
Isac
@Isac Oui, vous avez raison. L'accès par instance est déconseillé et ma réponse l'est aussi. Juste une autre perspective de la solution. 😀
legend80s