Last Updated: February 25, 2016
·
615
· hasenj

Convert a Constructor function to a regular function that can be called without new

In javascript, how can you convert a "constructor function" to a regular function that can be called without the new keyword?

The implementation

/**
    Take a constructor and return a function that can be called without new
    but have the same effect as calling the constructor with new.
    Useful for passing constructors to _.map, or passing arbitrary
    arguments to the constructor using fn.apply(null, arguments)
*/
function ctor_fn(ctor) {
    return function() {
        var object = Object.create(ctor.prototype);
        ctor.apply(object, arguments);
        return object;
    }
}

Motivation

Suppose you have a constructor function Button that takes two arguments: text and callback. You can create a new button object like this: new Button("Clickme", callback).

If you need to create a function that has the same effect as calling Button but without using the new keyword, you might do something like:

function CreateButton(text, callback) {
    return new Button(text, callback);
}

Easy. Now you can pass CreateButton around as a function that takes arguments and returns an object. You can even call it in this way:

CreateButton.apply(null, ["click me", callback]);

But now, what if you want to do the same for 10 other constructors? How do we generalize this approach?

Javascript's functions have .call and .apply, but there's nothing for doing the equivalent of .apply but with the effect of having the new keyword in front of it.

Actually the solution is simple: all you need to do is call apply and pass it as first argument some kind of an "empty" object, so to speak. However, in order to actually emulate the new keyword properly, this empty object has to have the prototype of the constructor function; otherwise it's not the same as calling the function with the new keyword.

Example

Open up the dev console of your browser and try the following:

First let's create some constructor function.

function Button(text, callback) {
    var self = this;
    self.text = text;
    var noop = function() {};
    self.callback = callback || noop;
    self.as_element = function() {
        var button = document.createElement("button");
        button.innerText = self.text;
        button.addEventListener("click", self.callback);
        return button;
    }
}

Nothing special there. Takes a label and a callback function, and provides a method to create a DOM button element based on that.

Now to test this works, create some instance and see if it behaves as expected:

> b1 = new Button("test1", function() { console.log("Test 1 has been clicked"); })
<- Button {text: "test1", callback: function, as_element: function}
> b1.as_element()
<- <button>​test1​</button>​
> b1.as_element().click()
Test 1 has been clicked
<- undefined

Note that I'm doing this in the Chrome dev console. Lines starting with <- indicate the value returned from the line entered above it.

Notice also how the Chrome console displays the button object (second line): it says Button { .... }. This is Chrome telling you which function was used to construct this object.

OK, so it works as expected when using the new keyword. Now let's turn it into a function using our ctor_fn above:

CreateButton = ctor_fn(Button);

See if it works:

> b2 = CreateButton("test2", function() { console.log("test2 clicked!! it works!!"); })
<- Button {text: "test2", callback: function, as_element: function}
> b2.as_element()
<- <button>​test2​</button>​
> b2.as_element().click()
test2 clicked!! it works!!
<- undefined

Works exactly the same as if it was called with new. Chrome even recognizes that it was constructed from the Button function! Why? Because we used Object.create(ctor.prototype) to create the object. Yea, we didn't use Button to create the object, but that's its prototype, so it's the same thing.

If you ask for the constructor of the object, you will get this:

> b2.constructor.name
<- "Button"

How about apply? Can we call CreateButton.apply to create the object and expect to behave correctly?

> b3 = CreateButton.apply(null, ["test3", function() { console.log("Test3 clicked! Apply also works!"); }])
<- Button {text: "test3", callback: function, as_element: function}
> b3.as_element()
<- <button>​test3​</button>​
> b3.as_element().click()
Test3 clicked! Apply also works!
<- undefined

Yep, it works!

So there you have it.

1 Response
Add your response

you could also add the following code into your constructor

if (!(this instanceof Button)) {
  return new Button(text, callback);
}

this automatically creates a new instance if you call Button("test", ...) without the new keyword

over 1 year ago ·