views:

356

answers:

5

In JavaScript, I want to create an object instance (via the new operator), but pass an arbitrary number of arguments to the constructor. Is this possible?

What I want to do is something like this (but the code below does not work):

function Something(){
    // init stuff
}
function createSomething(){
    return new Something.apply(null, arguments);
}
var s = createSomething(a,b,c); // 's' is an instance of Something


The Answer

From the responses here, it became clear that there's no in-built way to call .apply() with the new operator. However, people suggested a number of really interesting solutions to the problem.

My preferred solution was this one from Matthew Crumley (I've modified it to pass the arguments property):

var createSomething = (function() {
function F(args) {
    return Something.apply(this, args);
}
F.prototype = Something.prototype;

return function() {
    return new F(arguments);
}
})();
+1  A: 

You could do it like this:

var Ctor = function (a, b) {
    if (arguments.length === 0) {
        return;
    }

    this.a = a;
    this.b = b;
};

var c = new Ctor;
    c.constructor.apply(c, [3, 4]);

print(c.a); // 3
print(c.b); // 4

The downside is that the constructor function will be executed twice.

Ionuț G. Stan
Interesting. Looks good. I guess I was hoping for a mechanism that doesn't require the original constructor to be changed - only the calling function. But perhaps that's no doable. Thanks for this suggestion, Ionut.
Premasagar
You dont' really need to modify the original constructor unless it's doing heavy work. If I were to remove the check for `arguments.length` then `this.a` and `this.b` would be assigned to `undefined`. A constructor should do no real work. It should only set up state. Behaviour should be found in methods bound to the `prototype`.
Ionuț G. Stan
A: 

if you're interested in an eval-based solution

function createSomething() {
 var q = [];
 for(var i = 0; i < arguments.length; i++)
  q.push("arguments[" + i + "]");
 return eval("new Something(" + q.join(",") + ")");
}
stereofrog
......... Eeugh.
Tim Down
Using eval is slower and more error prone than using apply() directly.
Robert Koritnik
care to read the question? apply() won't work here
stereofrog
Thanks, stereofrog. That's a clever way to use eval to solve the problem. Ideally, I think I'd like to avoid the use of eval, though.
Premasagar
+2  A: 

You could move the init stuff out into a separate method of Something's prototype:

function Something() {
    // Do nothing
}

Something.prototype.init = function() {
    // Do init stuff
};

function createSomthing() {
    var s = new Something();
    s.init.apply(s, arguments);
    return s;
}

var s = createSomething(a,b,c); // 's' is an instance of Something
Tim Down
Yes, good idea. I could create an `init()` method and then use `apply()` on that. As with my comment on Ionut's approach, it's a bit of a shame that there's not a way to do this without modifying the architecture of the constructor. But this looks like a pragmatic solution.
Premasagar
+6  A: 

Here's a generalized solution that can call any constructor with an array of arguments:

function construct(constructor, args) {
    function F() {
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}

An object created by calling construct(Class, [1, 2, 3]) would be identical to an object created with new Class(1, 2, 3).

You could also make a more specific version so you don't have to pass the constructor every time. This is also slightly more efficient, since it doesn't need to create a new instance of the inner function every time you call it.

var createSomething = (function() {
    function F(args) {
        return Something.apply(this, args);
    }
    F.prototype = Something.prototype;

    return function(args) {
        return new F(args);
    }
})();

The reason for creating and calling the outer anonymous function like that is to keep function F from polluting the global namespace. It's sometimes called the module pattern.

Matthew Crumley
Thanks Matthew. Interesting to call a closure on the fly. Although your example shows the calling function allowing just one argument (an array of args), I guess this could be modified to have it pass on the `arguments` var instead.
Premasagar
There have been some excellent responses in this thread. I'm going to accept this one as my preferred solution, since it doesn't require modification of the original constructor (I didn't specify that as a requirement in my original question, but I appreciate it nevertheless). So the constructor can be written in any way, and the calling function written independently to add more convenience.
Premasagar
A: 

You can't call a constructor with a variable number of arguments like you want with the new operator.

What you can do is change the constructor slightly. Instead of:

function Something() {
    // deal with the "arguments" array
}
var obj = new Something.apply(null, [0, 0]);  // doesn't work!

Do this instead:

function Something(args) {
    // shorter, but will substitute a default if args.x is 0, false, "" etc.
    this.x = args.x || SOME_DEFAULT_VALUE;

    // longer, but will only put in a default if args.x is not supplied
    this.x = (args.x !== undefined) ? args.x : SOME_DEFAULT_VALUE;
}
var obj = new Something({x: 0, y: 0});

Or if you must use an array:

function Something(args) {
    var x = args[0];
    var y = args[1];
}
var obj = new Something([0, 0]);
Anthony Mills
Great, I earned the Disciplined badge for deleting my former wrong answer that had garnered 5 upvotes and one downvote. Oh well, here's the actual *useful* answer.
Anthony Mills
OK, fair enough. This basically restricts the number of args to just one (either an object or an array), but allows an arbitrary number of properties within it.
Premasagar
Yes. Well, it doesn't restrict the number of args at all, really (you just use one of the args as a container for optional arguments), it's just that an object or an array are generally the most useful containers. You'll often see this pattern in constructors; it allows named parameters (good for self-documenting source code) as well as optional parameters.
Anthony Mills