ES6: call class constructor without new keyword
Given a simple class
class Foo {
constructor(x) {
if (!(this instanceof Foo)) return new Foo(x);
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
Is it possible to call the class constructor without the new
keyword?
Usage should allow
(new Foo("world")).hello(); // "hello world"
Or
Foo("world").hello(); // "hello world"
But the latter fails with
Cannot call a class as a function
Classes have a "class body" that is a constructor.
If you use an internal constructor()
function, that function would be the same class body as well, and would be what is called when the class is called, hence a class is always a constructor.
Constructors requires the use of the new
operator to create a new instance, as such invoking a class without the new
operator results in an error, as it's required for the class constructor to create a new instance.
The error message is also quite specific, and correct
TypeError: Class constructors cannot be invoked without 'new'
You could;
- either use a regular function instead of a class1.
- Always call the class with
new
. - Call the class inside a wrapping regular function, always using
new
, that way you get the benefits of classes, but the wrapping function can still be called with and without thenew
operator2.
1)
function Foo(x) {
if (!(this instanceof Foo)) return new Foo(x);
this.x = x;
this.hello = function() {
return this.x;
}
}
2)
class Foo {
constructor(x) {
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
var _old = Foo;
Foo = function(...args) { return new _old(...args) };
As others have pointed out ES2015 spec strictly states that such call should throw TypeError, but at the same time it provides feature that can be used to achieve exactly the desired result, namely Proxies.
Proxies allows us to virtualize over a concept of an object. For instance they can be used to change some behaviour of particular object without affecting anything else.
In your specific use case class Foo
is Function object
which can be called -- this normally means that body of this function will be executed. But this can be changed with Proxy
:
const _Foo = new Proxy(Foo, {
// target = Foo
apply (target, thisArg, argumentsList) {
return new target(...argumentsList);
}
});
_Foo("world").hello();
const f = _Foo("world");
f instanceof Foo; // true
f instanceof _Foo; // true
(Note that _Foo
is now the class you want to expose, so identifiers should probably be the other way round)
If run by browser that support Proxies, calling _Foo(...)
will now execute apply
trap function instead of the orignal constructor.
At the same time this "new" _Foo
class is indistinguishable from original Foo
(apart from being able to call it as a normal function). Similarily there is no difference by which you can tell object created with Foo
and _Foo
.
The biggest downside of this is that it cannot be transpilled or pollyfilled, but still its viable solution for having Scala-like class apply in JS in the future.