How can I call a javascript constructor using call or apply?

fadedbee picture fadedbee · Jul 29, 2010 · Viewed 44.4k times · Source

How could I generalise the function below to take N arguments? (Using call or apply?)

Is there a programmatic way to apply arguments to 'new'? I don't want the constructor to be treated like a plain function.

/**
 * This higher level function takes a constructor and arguments
 * and returns a function, which when called will return the 
 * lazily constructed value.
 * 
 * All the arguments, except the first are pased to the constructor.
 * 
 * @param {Function} constructor
 */ 

function conthunktor(Constructor) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        console.log(args);
        if (args.length === 0) {
            return new Constructor();
        }
        if (args.length === 1) {
            return new Constructor(args[0]);
        }
        if (args.length === 2) {
            return new Constructor(args[0], args[1]);
        }
        if (args.length === 3) {
            return new Constructor(args[0], args[1], args[2]);
        }
        throw("too many arguments");    
    }
}

qUnit test:

test("conthunktorTest", function() {
    function MyConstructor(arg0, arg1) {
        this.arg0 = arg0;
        this.arg1 = arg1;
    }
    MyConstructor.prototype.toString = function() {
        return this.arg0 + " " + this.arg1;
    }

    var thunk = conthunktor(MyConstructor, "hello", "world");
    var my_object = thunk();
    deepEqual(my_object.toString(), "hello world");
});

Answer

kybernetikos picture kybernetikos · Jan 17, 2013

This is how you do it:

function applyToConstructor(constructor, argArray) {
    var args = [null].concat(argArray);
    var factoryFunction = constructor.bind.apply(constructor, args);
    return new factoryFunction();
}

var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);

Call is slightly easier

function callConstructor(constructor) {
    var factoryFunction = constructor.bind.apply(constructor, arguments);
    return new factoryFunction();
}

var d = callConstructor(Date, 2008, 10, 8, 00, 16, 34, 254);

You can use either of these to create factory functions:

var dateFactory = applyToConstructor.bind(null, Date)
var d = dateFactory([2008, 10, 8, 00, 16, 34, 254]);

or

var dateFactory = callConstructor.bind(null, Date)
var d = dateFactory(2008, 10, 8, 00, 16, 34, 254);

It will work with any constructor, not just built-ins or constructors that can double as functions (like Date).

However it does require the Ecmascript 5 .bind function. Shims will probably not work correctly.

A different approach, more in the style of some of the other answers is to create a function version of the built in new. This will not work on all builtins (like Date).

function neu(constructor) {
    // http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2
    var instance = Object.create(constructor.prototype);
    var result = constructor.apply(instance, Array.prototype.slice.call(arguments, 1));

    // The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
    return (result !== null && typeof result === 'object') ? result : instance;
}

function Person(first, last) {this.first = first;this.last = last};
Person.prototype.hi = function(){console.log(this.first, this.last);};

var p = neu(Person, "Neo", "Anderson");

And now, of course you can do .apply or .call or .bind on neu as normal.

For example:

var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");

I feel that the first solution I give is better though, as it doesn't depend on you correctly replicating the semantics of a builtin and it works correctly with builtins.