Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of .apply() with 'new' operator. Is this possible?

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 built-in 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);     } })(); 
like image 499
Prem Avatar asked Oct 22 '09 12:10

Prem


People also ask

What does apply () do in JavaScript?

Summary. The apply() method invokes a function with a given this value and arguments provided as an array. The apply() method is similar to the call() method excepts that it accepts the arguments of the function as an array instead of individual arguments.

What is the function of new operator?

The new operator lets developers create an instance of a user-defined object type or of one of the built-in object types that has a constructor function.

How do call () and apply () differ from each other?

The Difference Between call() and apply() The difference is: The call() method takes arguments separately. The apply() method takes arguments as an array. The apply() method is very handy if you want to use an array instead of an argument list.

What happens when you call a function with new keyword?

New keyword in JavaScript is used to create an instance of an object that has a constructor function. On calling the constructor function with 'new' operator, the following actions are taken: A new empty object is created.


2 Answers

With ECMAScript5's Function.prototype.bind things get pretty clean:

function newCall(Cls) {     return new (Function.prototype.bind.apply(Cls, arguments));     // or even     // return new (Cls.bind.apply(Cls, arguments));     // if you know that Cls.bind has not been overwritten } 

It can be used as follows:

var s = newCall(Something, a, b, c); 

or even directly:

var s = new (Function.prototype.bind.call(Something, null, a, b, c));  var s = new (Function.prototype.bind.apply(Something, [null, a, b, c])); 

This and the eval-based solution are the only ones that always work, even with special constructors like Date:

var date = newCall(Date, 2012, 1); console.log(date instanceof Date); // true 

edit

A bit of explanation: We need to run new on a function that takes a limited number of arguments. The bind method allows us to do it like so:

var f = Cls.bind(anything, arg1, arg2, ...); result = new f(); 

The anything parameter doesn't matter much, since the new keyword resets f's context. However, it is required for syntactical reasons. Now, for the bind call: We need to pass a variable number of arguments, so this does the trick:

var f = Cls.bind.apply(Cls, [anything, arg1, arg2, ...]); result = new f(); 

Let's wrap that in a function. Cls is passed as argument 0, so it's gonna be our anything.

function newCall(Cls /*, arg1, arg2, ... */) {     var f = Cls.bind.apply(Cls, arguments);     return new f(); } 

Actually, the temporary f variable is not needed at all:

function newCall(Cls /*, arg1, arg2, ... */) {     return new (Cls.bind.apply(Cls, arguments))(); } 

Finally, we should make sure that bind is really what we need. (Cls.bind may have been overwritten). So replace it by Function.prototype.bind, and we get the final result as above.

like image 172
user123444555621 Avatar answered Oct 18 '22 04:10

user123444555621


Here's a generalized solution that can call any constructor (except native constructors that behave differently when called as functions, like String, Number, Date, etc.) 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.

[UPDATE]

For those who want to use this in TypeScript, since TS gives an error if F returns anything:

function construct(constructor, args) {     function F() : void {         constructor.apply(this, args);     }     F.prototype = constructor.prototype;     return new F(); } 
like image 29
Matthew Crumley Avatar answered Oct 18 '22 06:10

Matthew Crumley