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]))();
2
  • 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. Commented Jul 2, 2013 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]))); Commented Jan 14, 2015 at 22:03

8 Answers 8

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

Sign up to request clarification or add additional context in comments.

7 Comments

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. jsfiddle.net/pSpz8/2
...or not (to my jsFiddle). After some sleep, I see that I missed the point of the question.
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?
@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.
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.
|
7

with ECMAscript 5 you can:

function createInstanceWithArguments (fConstructor, aArgs) {
    var foo = Object.create(fConstructor.prototype);
    fConstructor.apply(foo, aArgs);
    return foo;
}

Comments

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.

3 Comments

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: jsfiddle.net/pBJn4
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
Definitely agree, hacks like this are less than optimal. ES5 FTW.
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);
    };
}

Comments

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.

Comments

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.

Comments

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]);

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.