ES6: call class constructor without new keyword

97,501

Solution 1

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 the new 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) };

Solution 2

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.

Solution 3

Here's a pattern I've come across that really helps me. It doesn't use a class, but it doesn't require the use of new either. Win/Win.

const Foo = x => ({
  x,
  hello: () => `hello ${x}`,
  increment: () => Foo(x + 1),
  add: ({x: y}) => Foo(x + y)
})

console.log(Foo(1).x)                   // 1
console.log(Foo(1).hello())             // hello 1
console.log(Foo(1).increment().hello()) // hello 2
console.log(Foo(1).add(Foo(2)).hello()) // hello 3

Solution 4

No, this is not possible. Constructors that are created using the class keyword can only be constructed with new, if they are [[call]]ed without they always throw a TypeError1 (and there's not even a way to detect this from the outside).
1: I'm not sure whether transpilers get this right

You can use a normal function as a workaround, though:

class Foo {
  constructor(x) {
    this.x = x;
  }
  hello() {
    return `hello ${this.x}`;
  }
}
{
  const _Foo = Foo;
  Foo = function(...args) {
    return new _Foo(...args);
  };
  Foo.prototype = _Foo.prototype;
}

Disclaimer: instanceof and extending Foo.prototype work as normal, Foo.length does not, .constructor and static methods do not but can be fixed by adding Foo.prototype.constructor = Foo; and Object.setPrototypeOf(Foo, _Foo) if required.

For subclassing Foo (not _Foo) with class Bar extends Foo …, you should use return Reflect.construct(_Foo, args, new.target) instead of the new _Foo call. Subclassing in ES5 style (with Foo.call(this, …)) is not possible.

Solution 5

i just made this npm module for you ;)

https://www.npmjs.com/package/classy-decorator

import classy from "classy-decorator";
 
@classy()
class IamClassy {
    constructor() {
        console.log("IamClassy Instance!");
    }
}
 
console.log(new IamClassy() instanceof IamClassy());  // true 
 
console.log(IamClassy() instanceof IamClassy());  // true 
Share:
97,501
Mulan
Author by

Mulan

Updated on December 08, 2021

Comments

  • Mulan
    Mulan over 2 years

    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
    
  • loganfsmyth
    loganfsmyth almost 9 years
    I'm confused, why would you have the instanceof check, since you aren't even exposing the class? This answer doesn't really address the main issue.
  • Mulan
    Mulan almost 9 years
    This also doesn't work because Foo('world') instanceof Foo returns false.
  • Bergi
    Bergi over 8 years
    Hint: classes that have only static members should not be classes but plain objects. In case of only a single member, they shouldn't even be at all.
  • Tim
    Tim over 8 years
    normally I wouldn't define the create method static. in this example I'm using static, because the creator wants to create objects without the new keyword.
  • Bergi
    Bergi over 8 years
    Just if you didn't notice it: I'm talking about your Strategy classes. I hope you're not advocating to make create an instance method of those? static is totally fine.
  • Tim
    Tim over 8 years
    I'm talking about the strategies too. Don't really understand the problem. I only recommand to use a CreationStrategy, to create objects if they need "complex" logic.
  • Bergi
    Bergi over 8 years
    In JavaScript, if you need to do a thing, you can just do it. You don't need to write a class and create an instance of it for that. That's ridiculous bloat. Just use a simple function.
  • Tim
    Tim over 8 years
    Why not use some es6 features? It helps you to organize your code. A lot of people complain about javascripts syntax or exception etc. So why don't write clear code and increase the usability?
  • Bergi
    Bergi over 8 years
    Because declaring a class only to create a single function (and to call it a "method") does not organise code. Just declare the function. Do not use ES6 features only because they are there or because they make your code look like Java.
  • Tim
    Tim over 8 years
    That has nothing to do with java. And again: Its an example, to outsorce "complex" creation logic. "Please avoid extended discussions in comments. Would you like to...."
  • Bergi
    Bergi over 8 years
    Well all those classes start looking very much like Java… and that's not a good thing. There's nothing wrong with outsourcing complex logic, but you don't need classes for everything.
  • Bergi
    Bergi over 8 years
    And in this particular case, where the creation logic belongs to the class, I don't see any reason to outsource anything. Just leave it in that static create method.
  • Maxmaxmaximus
    Maxmaxmaximus about 7 years
    In the next versions will add call constructors: class Cat { call constructor(){ new Cat() } }
  • Stijn de Witt
    Stijn de Witt about 7 years
    This deserves points. I really wonder whether adding class to JS was an improvement. This shows what JS code should look like. For people wondering why there is no this anywhere, the created object is just using the x that was passed in to the 'constructor' (arrow function). Whenever it needs to be mutated, it returns a new object. The objects are immutable.
  • Kos
    Kos almost 7 years
    This is only working solution. All other answers do not work under some circumstances. Astonished how inaccurate StackOverflow rating system is that the only right answer in the bottom of the list.
  • yyny
    yyny over 6 years
    I wonder if it will optimize the functions into the prototype, though, or if it will create new functions for each object. Maybe with Object.freeze it will optimize?
  • Mulan
    Mulan over 6 years
    it will create new functions
  • Benjamin Riggs
    Benjamin Riggs over 6 years
    I actually like to use this pattern when I can, but subclassing is hell with it. Granted, it wouldn't be terribly difficult to add a couple language features to off-set that, but they don't exist.
  • Mulan
    Mulan over 6 years
    @BenjaminRiggs functional programmers don’t use subclassing - they use composition
  • Benjamin Riggs
    Benjamin Riggs over 6 years
    @naomik Building objects through composition is easy. The problem is that runtime-testing of whether or not an object satisfies an interface of a would-be class requires excessive thing.hasOwnProperty('attribute') || thing.hasOwnProperty('other_attribute') || ... checks. And even then, if those are method calls that have different type signatures, you're still screwed.
  • Mulan
    Mulan over 6 years
    javascript doesn’t have interfaces; i don’t know what you’re talking about
  • Benjamin Riggs
    Benjamin Riggs over 6 years
    Yes, Javascript lacking interfaces is part of the problem. Surely you're familiar with the general concept of an interface to a class. In Javascript, you test that with the instanceOf operator. When you're creating objects purely through composition, that operator doesn't work, so you have to degrade to a bunch of OR'd .hadOwnProperty calls. And even those can fail because methods on objects with identical names can return different things.
  • Benjamin Riggs
    Benjamin Riggs over 6 years
    Basically, functional programming is great when you're doing simple stuff, or when there's a robust type system. But doing complex functional programming in a language that uses classes as its type system, without using classes, is hard.
  • Mulan
    Mulan over 6 years
    I guess I don’t understand what you’re getting at - I haven’t used instanceof or .hasOwnProperty in quite some time; not missing them - To me it sounds like your issue is trying to enforce an interface in a language without interfaces. I don’t share your struggle so I’m afraid I don’t know how to help you. Sorry :/
  • Benjamin Riggs
    Benjamin Riggs over 6 years
  • kasbah
    kasbah over 6 years
    Currently node (v9.4.0) doesn't seem to properly support the arguments spread operator and it was causing me issues. I made a version based on the transpiled output of the classy-decorator mentioned in another answer. ``` function bindNew(Class) { function _Class() { for ( var len = arguments.length, rest = Array(len), key = 0; key < len; key++ ) { rest[key] = arguments[key]; } return new (Function.prototype.bind.apply(Class, [null].concat(rest)))(); } _Class.prototype = Class.prototype; return _Class; } ```
  • adeneo
    adeneo over 6 years
    @wandalen - it's clearly not the only working answer, in fact the right answer to the question is just "no, it's not possible". This is a different answer, that uses proxies instead of instances created with new, and it's a neat way to deal with the problem.
  • Babakness
    Babakness about 6 years
    The problem with technique is that each time Foo is invoked, it has to create all methods again. With classes, the prototype methods are efficiently shared between instances without having to re-create then per instance. Because the methods are re-created, you use up more memory as well. For production purposes, it is better to use something similar to the answer by Tim and use a method to create a new class.
  • Babakness
    Babakness about 6 years
    I've added an answer using of and references to the Fantasy-Land specs
  • Babakness
    Babakness about 6 years
    @naomik I've added a new answer stackoverflow.com/a/48911634/1560484, this one is similar to the one here except it doesn't create new functions each time. :-)
  • Mulan
    Mulan about 6 years
    I think I'm seeing a fixable problem, but I could be wrong. Each time we apply Right (eg Right(1), Right(2)), the Object.entries($methods).reduce bit is called. I think you intend to perform this reduce only once. Is that correct?
  • Babakness
    Babakness about 6 years
    @naomik Thanks! Hmm... You still need to bind the new value(s) in the functor container to the methods on the functor you return? I just optimized the code by placing the reducer outside of the reduce function to prevent it from being re-created each call.
  • Mulan
    Mulan about 6 years
    Hmm indeed... but it makes sense at a fundamental level: Just taking map: (x,f) => Right(f(x)), if x is ever going to represent a different value, map must be re-binded with that value. Re-binding creates a new function, so we're back in the same boat.
  • Babakness
    Babakness about 6 years
    I just did some reading--you are right about that, I'll update my answer--in a world were bind is optimized to only partially apply to a function without re-creating it, maybe this code will become the new fashion :-)
  • Mulan
    Mulan about 6 years
    I'm going to play around with it a bit. Your edit still calls the Object.entries( $methods ).reduce( each time we construct a new value. Binding delays evaluation, so you'd have to address this in a different way. Thanks for sharing this fun exercise.
  • Babakness
    Babakness about 6 years
    So I can know about it, please share a link here to whatever you come up with while experimenting!
  • Mulan
    Mulan about 6 years
  • Daniel Sokolowski
    Daniel Sokolowski about 6 years
    This syntax made no sense to me until I realized it is equivalent to const Foo = x => {return {x, ...}}.
  • Mulan
    Mulan about 6 years
    Daniel, correct. It’s a function that returns an object ^^
  • jordanbtucker
    jordanbtucker over 5 years
    If the class is declared first, then you don't need to use different names for the Proxy and class. class Foo {}; const Foo = new Proxy(Foo, {apply(target, thisArg, args) { return new target(...args) }}). However, Foo now references the Proxy instead of the original class.
  • kinger6621
    kinger6621 over 5 years
    This is an interesting pattern. Might use this one somewhere soon. A really cool idea and simple. Could make some cool helper functions with this pattern.
  • mhulse
    mhulse about 5 years
    In your third example, I see: static of(x) { new _Foo(x)} … What is the purpose of the underscore? Sorry if I am missing something obvious here. Thanks for the example!
  • Tomáš Hübelbauer
    Tomáš Hübelbauer almost 5 years
    @Maxmaxmaximus I think you should post that as an answer and add a source. It was news to me and very interesting.
  • Griffork
    Griffork over 4 years
    This was the only solution that worked for what I wanted, as due to making a dynamic class heirarchy (mixins defined as classes instead of functions) I needed to be able to instantiate the base class with the child class' prototype.
  • Admin
    Admin about 3 years
    One of the most underrated answers in the history of answering.
  • Mulan
    Mulan almost 3 years
  • smallscript
    smallscript over 2 years
    On QKS Smalltalk engine [I wrote] from 1998 (unchanged since then) run today on 2.60 GHz Dell Laptop: => [100_000 fibonacci] millisecondsToRun => 286 ms => 100_000 fibonacci asString size => 20899 FYI: V8 was born of Animorphic Smalltalk 1994 (Lars Bak) => Java HotSpot => V8 JavaScript. Try running "30000 factorial" if you want to see how good-or-bad the GC and BigInt system is doing on JavaScript. (p.s., I was lead JavaScript architect at Microsoft for many years).
  • Mulan
    Mulan over 2 years
    impressive! i've always wanted to learn smalltalk but never into it. do you have any recommendations for getting smalltalk up and running in a modern macos environment?
  • smallscript
    smallscript over 2 years
    Check this out: wiki.squeak.org/squeak/2801
  • trusktr
    trusktr over 2 years
    Although that example works, it will break if someone tries class Bar extends Foo {} because it will no longer extend the expected class.
  • Maciej Krawczyk
    Maciej Krawczyk over 2 years
    What's wrong with the new keyword in the first place? For some reason it's considered a bad practice to use class for something of which no reference is held (side effects) but why? The alternatives are uglier and some are less performant too.
  • kungfooman
    kungfooman almost 2 years
    This code just throws an error: VM60:4 Uncaught TypeError: Found non-callable @@iterator at reducer (<anonymous>:4:71) at Array.reduce (<anonymous>)