28

I know the possibility to call a function with an array of arguments with apply(obj,args); Is there a way to use this feature when creating a new instance of a function?

I mean something like this:

function A(arg1,arg2){
    var a = arg1;
    var b = arg2;
}

var a = new A.apply([1,2]); //create new instance using an array of arguments

I hope you understand what i mean... ^^^

Thanks for your help!

Solved!

I got the right answer. To make the answer fit to my question:

function A(arg1,arg2) {
    var a = arg1;
    var b = arg2;
}

var a = new (A.bind.apply(A,[A,1,2]))();
Van Coding
  • 24,244
  • 24
  • 88
  • 132
  • 1
    Important note: it doesn't matter what the first element of that array is. For example, `new (A.bind.apply(A,['cats',1,2]))();` will work just as well. The reason for this is that `new` ignores the `this` value provided to bind. – Nikolai Jul 02 '13 at 22:26
  • 2
    With the shorthand version provided, you don't need the last two brackets '()'. Thus it could be: `var a = new (A.bind.apply(A, [null].concat([1, 2])));` – Daniel Jan 14 '15 at 22:03

8 Answers8

16
var wrapper = function(f, args) {
    return function() {
        f.apply(this, args);
    };
};

function Constructor() {
    this.foo = 4;
}
var o = new (wrapper(Constructor, [1,2]));
alert(o.foo);

We take a function and arguments and create a function that applies the arguments to that function with the this scope.

Then if you call it with the new keyword it passes in a new fresh this and returns it.

The important thing is the brackets

new (wrapper(Constructor, [1,2]))

Calls the new keyword on the function returned from the wrapper, where as

new wrapper(Constructor, [1,2])

Calls the new keyword on the wrapper function.

The reason it needs to be wrapped is so that this that you apply it to is set with the new keyword. A new this object needs to be created and passed into a function which means that you must call .apply(this, array) inside a function.

Live example

Alternatively you could use ES5 .bind method

var wrapper = function(f, args) {
    var params = [f].concat(args);
    return f.bind.apply(f, params);
};

See example

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • Very nice. +1 Building off of yours, it may be desirable to have `wrapper` return the function to a variable with the identifier you want so you can call `new` from that, giving `instanceof` the result you'd expect. http://jsfiddle.net/pSpz8/2/ – user113716 Feb 20 '11 at 03:37
  • ...or not (to my jsFiddle). After some sleep, I see that I missed the point of the question. – user113716 Feb 20 '11 at 15:33
  • 4
    Thanks, this is really good. But the problem of this is, that the Object that is generated, is not of type Constructor. Is there a way to avoid this? – Van Coding Feb 21 '11 at 12:17
  • @FlashFan that's difficult to do without native `.bind` support. Alternatively you can start building it into the prototype chain but that just makes a mess of your prototype chain. See edited question though. – Raynos Feb 21 '11 at 13:54
  • Hey this is a really great trick! Thanks very much for that! It took me minutes to understand it :P I've added this as a "one-liner" to my question ;) – Van Coding Feb 21 '11 at 19:32
  • @Raynos - Copying over the prototype works for me (see below). Can you think of any other gotchas? – Jon Jaques May 01 '13 at 18:57
  • 1
    Important note: it doesn't matter what array you concat with, as long as it contains exactly one element. For example, `var params = ['cats'].concat(args);` will work just as well. The reason for this is that `new` ignores the `this` value provided to bind. – Nikolai Jul 02 '13 at 22:24
7

with ECMAscript 5 you can:

function createInstanceWithArguments (fConstructor, aArgs) {
    var foo = Object.create(fConstructor.prototype);
    fConstructor.apply(foo, aArgs);
    return foo;
}
sqykly
  • 1,586
  • 10
  • 16
6

@Raynos answer works well, except that the non-ES5 version is missing the constructor's prototype after instantiation.

Here's my updated cApply method:

var cApply = function(c) {
  var ctor = function(args) {
    c.apply(this, args);
  };
  ctor.prototype = c.prototype;
  return ctor;
};

Which can be used like this:

var WrappedConstructor = cApply(Constructor);
var obj = new WrappedConstructor([1,2,3]);

// or inline like this.    
var obj2 = new (cApply(Constructor))([1,2,3]);

JSFiddle for reference.

Jon Jaques
  • 4,262
  • 2
  • 23
  • 25
  • Best solution - I tweaked your solution to not have to pass an array, but use the wrapped constructor like normal, eg. `new WrappedConstructor(1,2,3)`. I also added the constructor to the prototype for good measure - **forked jsfiddle: http://jsfiddle.net/pBJn4/** – Ali Nov 15 '13 at 05:21
  • 2
    Unfortunately there doesn't seem a way to keep the original constructor for the return type, which would be bad for `obj instanceof Constructor` cases – Ali Nov 15 '13 at 05:42
  • Definitely agree, hacks like this are less than optimal. ES5 FTW. – Jon Jaques Nov 15 '13 at 17:16
3

You can use Object.create() to build the new instance, and call the constructor on the instance after that:

var args = [1,2,3];
var instance = Object.create(MyClass.prototype);
MyClass.apply(instance, args);

or in a single line:

var instance = MyClass.apply(Object.create(MyClass.prototype), args);

but don't forget return this; in MyClass by this single line solution.

If you don't have Object.create(), then it is very simple to write one:

Object.create = function (source){
    var Surrogate = function (){};
    Surrogate.prototype = source;
    return new Surrogate();
};

You can optionally write an Function.newInstance() or a Function.prototype.newInstance() function:

Function.newInstance = function (fn, args){
    var instance = Object.create(fn.prototype);
    fn.apply(instance, args);
    return instance;
};

so var instance = Function.newInstance(MyClass, args);.

note: It is not recommended to override native classes.

inf3rno
  • 24,976
  • 11
  • 115
  • 197
3

You can curry the functions:

function A(arg1, arg2) {
    // ...
}

function A12() {
    A(1, 2);
}

You could even build a curry factory:

function A_curry() {
    var args = arguments;
    return function () {
        A.apply(null, args);
    };
}
sdleihssirhc
  • 42,000
  • 6
  • 53
  • 67
0

What if you have your object class name stored in a variable called className ?

var className = 'A'

According to this answer here it all becomes very clean and simple using ECMAScipt5's Function.prototype.bind method.

With an array of arguments:

new ( Function.prototype.bind.apply( className, arguments ) );

Or with an argument list:

new ( Function.prototype.bind.call( className, argument1, argument2, ... ) );

A single line, no need for a wrapper.

Community
  • 1
  • 1
Wilt
  • 41,477
  • 12
  • 152
  • 203
0

I just had the same issue and also wanted to preserve prototype properties of the target object. After looking at the answers, just came up with a rather simple solution. Thought I might as well share it for any future seeker :)

// Define a pseudo object that takes your arguments as an array
var PseudoObject = function (args) {
    TargetObject.apply(this, args);
};
// if you want to inherit prototype as well
PseudoObject.prototype = new TargetObject();

// Now just use the PseudoObject to instantiate a object of the the OtherObject
var newObj = new PseudoObject([1, 2, 3]);
0

It can now be done using the spread operator:

let a = new A(...[1,2]);

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#examples

dlporter98
  • 1,590
  • 1
  • 12
  • 18