2015-10-20

ECMAScript proposal: function-callable classes

This blog post describes the proposed ECMAScript feature “call constructor” (stage 1).

Classes can’t be function-called in ES6

In ECMAScript 6, classes can’t be function-called:

    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
    }
    let p1 = new Point(1, 2); // OK
    let p2 = Point(3, 4); // TypeError

For ES6, there was no categorical objection to classes being function-called. The error was only thrown so that handling function calls via classes could be added later.

Function-callable constructors in ES6

If you want to implement a constructor Point that can be both function-called and constructor-called, you have no choice but to use a constructor function in ES6:

    function Point(x, y) {
        if (!new.target) {
            // Point was function-called
            return new Point(x, y);
        }
        this.x = x;
        this.y = y;
    }
    let p1 = new Point(1, 2); // OK
    let p2 = Point(3, 4); // OK

Function-callable classes via the proposed call constructor

The proposal is about allowing classes to handle function calls via the pseudo-method call constructor():

    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        call constructor(x, y) {
            return new Point(x, y);
        }
    }
    let p1 = new Point(1, 2); // OK
    let p2 = Point(3, 4); // OK

Things of note:

  • A call constructor is not inherited by subclasses, you have to put it into each class you want to function-call.
  • A super() call is a compile-time error inside a call constructor (just like it is in normal methods). That is done to keep options open w.r.t. enabling super() in call constructors in the future.

Possible future additions to the proposal

New meta-properties

In a future version of the proposal, two meta-properties will probably be added:

  • class.current refers to the current class (a function).
  • class.extends refers to the super-class of the current class.

class.current will help with forwarding from the call constructor to the constructor:

    class Foo {
        constructor(···) {
            ···
        }
        call constructor(...args) {
            return new class.current(...args);
        }
    }

A decorator for making classes function-callable

Another possibility is a class decorator that enables function-calling by forwarding from the call constructor to the constructor. For example:

    @callConstructor
    class Foo {
        constructor(···) {
            ···
        }
    }

Internally, this class looks like the previous example.

Further reading

No comments: