Proposal for a static constructor and static member variables

# Brandon Andrews (3 months ago)

The initial proposal for discussion is below:

sirisian/ecmascript-static-constructor

I don't believe this conflicts with other proposals yet, nor any future proposals. Essentially it adds static members in a very compact syntax with minimal grammar changes required.

What I foresee happening years from now is public, private, and static member syntax will be added. Much like how public members are defined in the constructor for the moment the static constructor would be the way to define static members of the class for now.

Thoughts?

# Logan Smyth (3 months ago)

So we're on the same page, in the active proposal list ( tc39/proposals) you'll find

Public Fields includes a syntax for static properties that would convert your example to something like

class Multiton {
    static instances = {};

    constructor(name) {
        if (Multiton.instances.hasOwnProperty(name)) throw `Multiton name
already used`;

        Multiton.instances[name] = this;
    }
}

though I don't know I'd personally do this in this instance, rather than putting it outside the class, like

const instances = {};
class Multiton {
    constructor(name) {
        if (instances.hasOwnProperty(name)) throw `Multiton name already
used`;

        instances[name] = this;
    }
}

Keep in mind that if something is a "static" in the ES6 class method sense, you will never be able to do this.instances to get it because the property does not live on this, it lives on the constructor. I'm unsure if the private fields proposal would allow static #instance = {}; as a static with #instance[name] = this; inside the constructor.

# Brandon Andrews (3 months ago)

The public one is the one I was thinking of when I said it shouldn't conflict. (I don't see the private spec going anywhere to be honest with that syntax, but if it does that would be interesting).

As for the conversion, yes that would be a good conversion for the first example. In the second example putting things outside places them in a different scope. Imagine you had multiple classes defined and they had their own instances member.

As for static constructors a more contrived example would be a memoization operation where one does initial work or generation in the static constructor rather than initialization.

class MathLibrary
{
    static table = {};
    static constructor()
    {
        // Common cases
        for (let i = 0; i < 100; ++i)
        {
            MathLibrary.table[i] = // something complex and time consuming
        }
    }
    static Calculate(x)
    {
        if (MathLibrary.table.hasOwnProperty(x))
        {
            return MathLibrary.table[x];
        }
        else
        {
            let complexCalculation = // something complex and time consuming
            MathLibrary.table[x] = complexCalculation;
            return complexCalculation;
        }
    }
}

Keep in mind that if something is a "static" in the ES6 class method sense, you will never be able to do this.instances to get it because the property does not live on this, it lives on the constructor.

I'll have to think about that. Might need another proposal. I always like to think about static methods and variables as being shared among all instances. Would be nice to get that kind of meaning also. Essentially aliases. Accessing static methods inside of a instance methods requires using the type name which I find less than ideal. Probably a personal preference though.

# Reinis Ivanovs (3 months ago)

Having a static constructor makes things more confusing, since now static and instance constructors would need to be differentiated, and its value is kind of unclear. Your example also doesn't make sense, since you're accessing the static properties from the instance constructor using this, which would refer to the instance, not the constructor.

# T.J. Crowder (3 months ago)

It's an interesting idea and certainly some other languages (Java, C#), have static constructors; others (C++) have lived without. I don't recall the runtime details for C#, but certainly Java defers calling static constructors until actually initializing the class, which is indeed later than JavaScript's class definition time, at least for main scripts.

Given modules, though, I wonder if static constructors of the style you're describing (where they aren't triggered until the first access to the class) are worth the overhead? Implementing would either mean trapping access to the class constructor's binding and doing init on first post-definition access (but I suspect that would be prone to being triggered early a lot), or effectively proxying the class constructor and using the get and some other traps (under the covers, granted, but the overhead is still there). That sounds heavy to me. Whereas using a module gives us the lazy init by the nature of modules -- if they're never imported, they're never run:

export default class MathLibrary {
    // (Assumes the public class properties proposal)
    static table = (() => {
        const t = [];
        // ...time-consuming work initializing t here...
        return t;
    })();

    static calculate() {
        // ...can use MathLibrary.table here...
    }
}

(I wouldn't make table a static property at all -- exposes internal details of the class -- but I wanted to stay close to your example.)

I wouldn't mind some syntactic sugar to make that prettier, but I'm not sure we need it to be deferred to first use of the class; I'm happy for it to run at class definition time. Using a module very nearly does that deferral already. The use case for deferred static construction becomes a module that imports the module above but then conditionally doesn't use it (and we only want to do the time-consuming work in the branch that does), which starts getting a bit...narrow?

Putting modules to the side for now:

From the current proposal:

The Singleton and Multiton patterns are two instances where this feature can be utilized.

I assume you mean a singleton that isn't just a simple object, e.g., a class for which new X returns the same instance every time, or an object with a property or getter that always returns the same instance, with lazy initialization. Although a static constructor could be used for that, I'm not sure it brings a lot to the table vs. existing mechanisms, for instance:

let instance = null;
class Singleton {
    constructor() {
        if (!instance) {
            instance = this;
            console.log("initialization");
        }
        return instance;
    }
}

// Logs "initialization" during first construction, and not the second
const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2); // true

or

let instance = null;
const Singleton = {
    get instance() {
        if (!instance) {
            instance = {}; // or whatever
            console.log("initialization");
        }
        return instance;
    }
};

// Logs "initialization" once, then logs true
console.log(Singleton.instance === Singleton.instance);

or

const Singleton = {
    get instance() {
        const instance = {}; // or whatever
        console.log("initialization");
        Object.defineProperty(Singleton, "instance", {
            value: instance,
            enumerable: true
        });
        return instance;
    }
};

// Logs "initialization" once, then logs true
console.log(Singleton.instance === Singleton.instance);

Granted that last one is several lines, but if it's a pattern you use often, a helper function solves that.

Similarly for Multiton. Can you elaborate how a static constructor applies?

On this and static properties:

[From Logan Smyth]

Keep in mind that if something is a "static" in the ES6 class method sense, you will never be able to do this.instances to get it because the property does not live on this, it lives on the constructor.

I'll have to think about that. Might need another proposal.

More accurately, you can refer to static properties via this, but only from within static methods called in the normal way:

class Example {
    // (Assumes public class fields proposal goes forward)
    static a = 42;

    static announce() {
        console.log(`${Example.a}, ${this.a}`);
    }
}

Example.announce();        // "42, 42"
Example.announce.call({}); // "42, undefined"
const a = Example.announce;
a();                       // Error in strict mode
                           // "42, undefined" (probably) in loose mode

So for instance, you could have used this within your static MathLibrary.Calculate function. You can't from within a class constructor as in your current proposal text. (Side note: I'd recommend sticking to standard naming in these discussions -- e.g., calculate rather than Calculate -- to avoid confusion.)

In a method where this is an instance of the class, you can of course use this.constructor.x to refer to a static property x, if you want to avoid repeating the class name.

Best,

-- T.J.

# Isiah Meadows (3 months ago)

There is this proposal for static blocks, which IMHO would fit better with the intent: littledan/proposal-class-static-block

Class initialization isn't the same as object initialization. Classes are only initialized once, and parameters don't make sense, so it doesn't make sense to make it a method-like construct. Getters are still function-like, and they're readable via descriptors. So such a "static constructor" would in practice be closer to a special IIFE that's run in the context of the class.

In addition, does it make sense to re-run the static block with subclasses? Generally, it doesn't, because subclasses prototypically inherit their parent (you already get those for free), and it could break the intuitive behavior of using them with the private field proposal to expose private properties to specific classes and functions outside it. (Those functions would be created once per class

  • subclass, so it'd waste a lot of time for no otherwise good reason.)

So given it's best to only run it once ever, and parameters don't make sense, might as well just make it a declarative block.

Isiah Meadows me at isiahmeadows.com