93

I'm making a Javascript class and I'd like to have a public static field like in Java. This is the relevant code:

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

This is the error I get:

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

It looks like ES6 modules don't allow this. Is there a way to get the desired behavior or do I have to write a getter?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
aebabis
  • 3,657
  • 3
  • 22
  • 40

6 Answers6

144

You make "public static field" using accessor and a "static" keyword:

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

Agent.CIRCLE; // 1

Looking at a spec, 14.5 — Class Definitions — you'd see something suspiciously relevant :)

ClassElement[Yield] :
  MethodDefinition[?Yield]
  static MethodDefinition[?Yield] ;

So from there you can follow to 14.5.14 — Runtime Semantics: ClassDefinitionEvaluation — to double check if it really does what it looks like it does. Specifically, step 20:

  1. For each ClassElement m in order from methods
    1. If IsStatic of m is false, then
      1. Let status be the result of performing PropertyDefinitionEvaluation for m with arguments proto and false.
    2. Else,
      1. Let status be the result of performing PropertyDefinitionEvaluation for m with arguments F and false.
    3. If status is an abrupt completion, then
      1. Set the running execution context’s LexicalEnvironment to lex.
      2. Return status.

IsStatic is defined earlier in 14.5.9

ClassElement : static MethodDefinition
Return true.

So PropertyMethodDefinition is called with "F" (constructor, function object) as an argument, which in its turn creates an accessor method on that object.

This already works in at least IETP (tech preview), as well as 6to5 and Traceur compilers.

kangax
  • 38,898
  • 13
  • 99
  • 135
  • For anyone else looking, the static accessor properties aren't supported in Node yet. :-/ https://kangax.github.io/compat-table/es6/#class_computed_static_accessor_properties – David Hernandez Jun 06 '15 at 19:05
  • 2
    As of at least Node.js 6.x+, this is supported. – NuSkooler Feb 18 '17 at 19:46
  • Note that if you're using flow, you have to add a line `unsafe.enable_getters_and_setters=true` to your .flowconfig under `[options]` (which is annoying). – kris Mar 01 '18 at 15:29
  • This won't work for me i'm getting ``` Unhandled rejection TypeError: Cannot set property dataHashKey of class Collections { api_1 | static get dataHashKey() { api_1 | return 'collections'; api_1 | } ``` – Pavan Nov 14 '18 at 10:11
  • Kangax, maybe I am wrong about this but I think you are the developer behind fabric, or at least you are very versed on fabric. I have two unanswered questions that I need to solve in order to go on with my project. Could you be so kind and take a look at them? – SIMBIOSIS surl Aug 23 '21 at 02:46
64

Since ECMAScript 2022, you can do something like this, similar to traditional class-oriented languages like Java and C#:

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'
    }
}

The above is equivalent to:

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;

The features were added in the "Static Class Features" and "Class Fields" proposals by Daniel Ehrenberg et al. Google Chrome (and new Edge) started supporting both proposals in version 72, equivalent to Node.js 12+. Firefox supports public instance fields since version 69 and static instance fields since version 75. Safari supports both since version 14.1. See more info at caniuse.com.

For older browsers that don't yet support these features, you can use Babel to transpile class fields. This requires @babel/plugin-proposal-class-properties to be enabled (enabled by default in @babel/plugin-env starting from v7.14.0).


Compared to @kangax's solution of declaring a getter, this solution can also be more performant, since here the property is accessed directly instead of through calling a function.


Edit: A unified class fields proposal is now at stage 3.

Edit (February 2020): The static class features have been split out into a different proposal. Thanks @GOTO0!

Edit (March 2021): With the exception of Safari, all major browsers released after April 2020 now support this feature!

Edit (June 2021): Both proposals are accepted by TC39, the ECMAScript language committee, and Safari shipped this feature in version 14.1!

Timothy Gu
  • 3,727
  • 28
  • 35
  • 2
    I think the relevant proposal is actually [this one](https://github.com/tc39/proposal-static-class-features/) (_Static class features_). – GOTO 0 Jul 04 '19 at 21:16
29

In current drafts of ECMAScript 6 (as of February 2015), all class properties must be methods, not values (note in ECMAScript a "property" is similar in concept to an OOP field, except the field value must be a Function object, not any other value such as a Number or Object).

You can still specify these using traditional ECMAScript constructor property specifiers:

 class Agent {
 }
 Agent.CIRCLE = 1;
 Agent.SQUARE = 2;
 ...
Dai
  • 141,631
  • 28
  • 261
  • 374
  • 11
    Note that the ES6 `class` syntax is just syntactic sugar for traditional JS constructor functions and prototypes anyway. – Matt Browne Feb 11 '15 at 02:54
  • I think you'd want to put those properties on the prototype and not on the constructor in order for them to be visible via property references from instances. – Pointy Feb 11 '15 at 03:02
  • @Pointy I inferred that the OP is trying to store constants for reference (almost like a C#/.NET `enum`). – Dai Feb 11 '15 at 03:04
  • 2
    @MattBrowne Yes, but to be clear `class` syntax also has certain nuanced differences. For example, a method declared with `Class.prototype.method = function () {};` is *enumerable* (visible with for-in loops), while `class` methods are not enumerable. – Timothy Gu May 13 '17 at 22:35
4

To get full advantage of static variable I followed this approach. To be more specific, we can use it to use private variable or having only public getter, or having both getter or setter. In the last case it's same as one of the solution posted above.

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'];

I could create another class extending Url and it worked.

I used babel to convert my ES6 code to ES5

SM Adnan
  • 555
  • 2
  • 10
0

Or Put your static variables in a separate module. The module variables preserve values.

myClassDeps.js

const myClassDeps = {};
export default myClassDeps;
myClassDeps.myStaticVariable = 123;

myClass.js

import myClassDeps from "./myClassDeps.js";

export class MyClass {
  constructor() {
    this.deps = myClassDeps;
  }

  get myStaticVariable() {
    return this.deps.myStaticVariable;
  }

  set myStaticVariable(value){
    this.deps.myStaticVariable = value;
  }
}
AmirHossein Rezaei
  • 1,086
  • 1
  • 16
  • 20
-1

@kangax 's answer does not imitate the whole static behaviour of traditional OOP language's, because you cannot access the static property by it's instance like const agent = new Agent; agent.CIRCLE; // Undefined

If you want to access static property just like OOP's, here is my solution:

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

NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

Test code as follows.

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
legend80s
  • 287
  • 3
  • 8
  • 1
    Accessing a `static` field by an instance would be rather uncommon wouldn't you say ? In some languages, such as Java, IDE's actually issue a warning/hint if you do something like that. – Isac May 13 '18 at 22:31
  • @Isac Yes you are right. Accessing by instance is discouraged and so is my answer. Just another perspective of the solution. – legend80s Dec 14 '18 at 03:50