21

In ES6, I can create static methods like below. But I need to define a static constructor but no success. I need something that runs only once when the class is loaded. I Is there any way to implement something like this ?

class Commander{

    static onData(){
         console.log("blabla");
    }
}
Barış Velioğlu
  • 5,709
  • 15
  • 59
  • 105
  • You need do some logic when is the first instance of your class created? – madox2 Feb 02 '16 at 14:39
  • 1
    You could have a check in your constructor, if a static variable is not set, do your initialisations then set that static variable to true. – XCS Feb 02 '16 at 14:40
  • Actually I dont get an instance of this class. Directly I am using it like Commander.onData() etc... – Barış Velioğlu Feb 02 '16 at 14:40
  • 3
    @RyuKaplan If you never instantiate the class, this means you are actually only using it as a namespace. You could have an initialize methond in that namespace and simply call it somewhere else in your code. – XCS Feb 02 '16 at 14:41
  • @Cristy That is the last way I want to do. – Barış Velioğlu Feb 02 '16 at 14:43
  • It seems like you really just want an object with a function int it. – Evan Davis Feb 02 '16 at 14:43
  • 2
    Wrap the class declaration in a function, initialize things in that function, and call the function. The function finishes by exporting the class. – Pointy Feb 02 '16 at 14:44
  • 3
    Like others have said, classes aren't the right tool for the job here – Keith Rousseau Feb 02 '16 at 14:49

5 Answers5

21

It does seem neater to have class-setup code inside the class body so the "class expression" is self-contained. ES6 accepts the syntax static constructor() {/* do stuff */} in a class body but never runs it. Perhaps it is for future language expansion? Anyway, here is one way to achieve the desired result. The trick is to initialize a static property with an immediately-executed function expression that does your class setup:

class MyClass {
  static #staticConstructorDummyResult = (function() {
    console.log('static constructor called') // once!
  })()
  constructor () {
    console.log('instance constructor called')
  }
}
let obj = new MyClass(),
    obj2 = new MyClass()

Inside the "static constructor" you can add properties to the class object with MyClass.prop = value, or if you're keen to refer to MyClass as this, change the function expression to an arrow function expression.

The # makes staticConstructorDummyResult private - which should work in all major browsers now (thanks @Donald Duck).

Hugh Allen
  • 6,509
  • 1
  • 34
  • 44
  • 1
    "_if you don't mind requiring Chrome (it won't work in Firefox currently)_" [Today private fields (starting with `#`) work in all major browsers.](https://caniuse.com/mdn-javascript_classes_private_class_fields) – Donald Duck Sep 11 '22 at 15:28
13

I need something that runs only once when the class is loaded.

You shouldn't be using classes if you just use them as a bag of methods. Use an object instead. However, it's still possible to run such code. Just put it before or after the class definition.

console.log('before class is created')

class Foo {}

console.log('after class was created');
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
6

If you insist on a static constructor: Define a static method and invoke it after the class definition.

class Foo {
  static staticConstructor() {
    console.log('Foo has been constructed statically!');
  }
}

Foo.staticConstructor()

Of course this is not really necessary. Except mabye to clearly express the notion of a static constructor. However this smells like Java.

Felix proposed a fine solution by putting code before or after the class definition.

For example: Do you want to pre-calculate some static members? Just assign the calculation result after the class definition!

class Foo {}

Foo.preCalculated = calculate();

function calculate() {
  console.log('Do some hard work here');
  return 'PRECALCULATED';
}
nalply
  • 26,770
  • 15
  • 78
  • 101
  • What about inheritance, is there a way to call super.* in this context? – user1514042 May 31 '19 at 09:40
  • Good question, why not make it a separate question? Here my take: Each class has its own static constructor. If class A extends class B and they have both static constructors, then class B should be constructed first, then class A. One way to achieve this is putting class B before class A, so that when code for class A is running, class B is ready. You don't need `super` for static constructors. – nalply May 31 '19 at 11:04
  • Why don't I - I want my static ctor to do the same thing as an instance one, without the need for using custom methods. Using custom methods for objects creation has a missive drawback - you need to make your object consumers aware about that method, which is a leak of dry. I can put a simple example if you're interested. – user1514042 Jun 04 '19 at 09:51
  • Then just have code along the class definitions, exactly as Felix Kling proposed, so users don't need to invoke static constructors. – nalply Jun 04 '19 at 10:00
4

In ES2022 we now have static initialization blocks which look like this:

class Commander {
  static {
    // Arbitrary code goes in here and is run immediately
    // You can use `this` to reference the class (instead of having to use its name):
    this.foo = 'foo'; // sets a static property
  }
}
Inkling
  • 3,544
  • 4
  • 30
  • 44
  • This is the correct answer, IMO. The only caveat is that browsers and node only started supporting this around September 2021. – code.monger Apr 25 '23 at 15:34
2

With new class property initializers, you may not need a function at all (for simple expressions).

Initializers are = expressions in the class definition context; they act like as being an expression in the constructor, so this is defined (because initializers come after constructors chain).

class Toto {
    foo = 'bar'
    bar = this.foo
    baz = this.method()
    method(){ return 'baz' }
}
console.log( new Toto )
//> Toto {foo: "bar", bar: "bar", baz: "baz"}

Static initializers work the same way, but this is the actual constructor (class), the same way it is defined in a static method.

class Toto {
    static foo = 'bar'
    static bar = this.foo
    static baz = this.method()
    static method(){ return 'baz' }
}
console.dir( Toto )
//> class Toto {name: "Toto", foo: "bar", bar: "bar", baz: "baz", method: ƒ method()}

Using a parent class to declare static methods to be called during initialization is quite handy:

class Base extends HTMLElement {
    static define( tag )
    {
        return customElements.define( this, tag )
    }
}

//then

class MyElement extends Base {
    constructor(){ ... }
    static defined = this.define( 'my-el' )
}

You can also use static getters/setters:

/** utils */
const CSS = css=> { let s = new CSSStyleSheet; s.replaceSync(css); return s }

class Base extends HTMLElement {
    /**
     * node.shadowRoot getter to access shadowRoot with direct creation if not existing yet.
     * @exemple class extends HTMLElement { constructor(){ super(); this.shadowRoot.innerHTML = '...' } }
     * @exemple class extends HTMLElement { html = this.shadowRoot.innerHTML = '...' }
     */
    get shadowRoot()
    {
        return super.shadowRoot || this.attachShadow({mode:'open'})
    }
    adoptedCSS = this.shadowRoot.adoptedStyleSheets = [ this.constructor.css ]
    static set tag( v )
    {
        return customElements.define( this, v )
    }
}

//then

class MyElement extends Base {
    title = 'Default title'
    html = this.shadowRoot.innerHTML = `
        <div class=super>
            <h1>${this.title}</h1>
        </div>
    `
    $title = this.shadowRoot.querySelector('div > h1')

    static css = CSS`
        :host       { outline: 1px solid blue }
        div         { outline: 1px solid green }
    `
    static defined = this.tag = 'my-el'

    // static tag = 'my-el' << this won't work because initializers use  
    // Object.defineProperty and not a direct set, so the setter  
    // will be overwritten!
}

// Note: no need of any framework here

Support:

Sources:

chag
  • 3
  • 3
Thomas Di G
  • 261
  • 2
  • 9