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

How can I generalize the function below to accept N arguments? (Use call or application?)

Is there a programmatic way to apply arguments to the "new"? I do not want the constructor to be processed as a simple 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"); }); 
+78
javascript apply call
Jul 29 '10 at 12:35 on
source share
7 answers

Try the following:

 function conthunktor(Constructor) { var args = Array.prototype.slice.call(arguments, 1); return function() { var Temp = function(){}, // temporary constructor inst, ret; // other vars // Give the Temp constructor the Constructor prototype Temp.prototype = Constructor.prototype; // Create a new instance inst = new Temp; // Call the original Constructor with the temp // instance as its context (ie its 'this' value) ret = Constructor.apply(inst, args); // If an object has been returned then return it otherwise // return the original instance. // (consistent with behaviour of the new operator) return Object(ret) === ret ? ret : inst; } } 
+49
Jul 29 '10 at 12:53 on
source share

Here's 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]); 

The call is a little 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 any 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-in or constructors that can work as functions (e.g. Date).

However, this requires a function. Gaskets are likely to work incorrectly.

Another approach, more similar to the style of some other answers, is to create a functional version of the inline new . This will not work on all embedded devices (e.g. 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 usual.

For example:

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

I feel that the first solution I give is better, though, since it does not depend on you, correctly replicating the semantics of the built-in and working correctly with the built-in ones.

+88
Jan 17 '13 at 11:51
source share

In all cases, this function is identical to new . This is likely to be significantly slower than 999's answer. However, use it only if you really need it.

 function applyConstructor(ctor, args) { var a = []; for (var i = 0; i < args.length; i++) a[i] = 'args[' + i + ']'; return eval('new ctor(' + a.join() + ')'); } 

UPDATE: Once ES6 support is widely available, you can write the following:

 function applyConstructor(ctor, args) { return new ctor(...args); } 

... but you won’t need it, because the standard library function Reflect.construct() does exactly what you are looking for!

+15
Dec 12 '11 at 17:10
source share

Another approach, which requires changing the actual constructor, which is called, but seems to me cleaner than using eval () or introducing a new dummy function in the build chain ... Save your conthunktor function as

 function conthunktor(Constructor) { // Call the constructor return Constructor.apply(null, Array.prototype.slice.call(arguments, 1)); } 

And change the called constructors ...

 function MyConstructor(a, b, c) { if(!(this instanceof MyConstructor)) { return new MyConstructor(a, b, c); } this.a = a; this.b = b; this.c = c; // The rest of your constructor... } 

So you can try:

 var myInstance = conthunktor(MyConstructor, 1, 2, 3); var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6 
+4
Feb 04 '13 at 15:17
source share

Using a temporary constructor seems to be the best solution if Object.create not available.

If Object.create available, using it is a much better option. In Node.js, using Object.create , you get much faster code. Here's an example using Object.create :

 function applyToConstructor(ctor, args) { var new_obj = Object.create(ctor.prototype); var ctor_ret = ctor.apply(new_obj, args); // Some constructors return a value; make sure to use it! return ctor_ret !== undefined ? ctor_ret: new_obj; } 

(Obviously, the args argument is a list of arguments to apply.)

I had a piece of code that originally used eval to read the piece of data created by another tool. (Yes, eval is evil.) This will create a tree of hundreds of thousands of elements. Basically, the JavaScript engine was responsible for parsing and executing a bunch of new ...(...) expressions. I converted my system to analyze the JSON structure, which means that I need my code to determine which constructor calls for each type of object in the tree. When I ran the new code in my test suite, I was surprised to see a sharp slowdown relative to the eval version.

  • Test suite with eval version: 1 second
  • Test suite with JSON version using a temporary constructor: 5 seconds.
  • Test suite with JSON version using Object.create : 1 second.

A test suite creates several trees. I calculated that my applytoConstructor function applytoConstructor called about 125,000 times when the test package runs.

+3
May 13 '13 at 16:59
source share

In ECMAScript 6, you can use the spread operator to apply a constructor with a new keyword to an array of arguments:

 var dateFields = [2014, 09, 20, 19, 31, 59, 999]; var date = new Date(...dateFields); console.log(date); // Date 2014-10-20T15:01:59.999Z 
+3
Sep 20 '14 at 15:03
source share

In this case, there is an adjustable solution. For each class that you want to call using the apply or call method, you must call before converting ToAllowApply ('classNameInString'); the class should be in the same Scoope o global scoope (I'm not trying to send ns.className, for example ...)

There is a code:

 function convertToAllowApply(kName){ var n = '\n', t = '\t'; var scrit = 'var oldKlass = ' + kName + ';' + n + kName + '.prototype.__Creates__ = oldKlass;' + n + kName + ' = function(){' + n + t + 'if(!(this instanceof ' + kName + ')){'+ n + t + t + 'obj = new ' + kName + ';'+ n + t + t + kName + '.prototype.__Creates__.apply(obj, arguments);'+ n + t + t + 'return obj;' + n + t + '}' + n + '}' + n + kName + '.prototype = oldKlass.prototype;'; var convert = new Function(scrit); convert(); } // USE CASE: myKlass = function(){ this.data = Array.prototype.slice.call(arguments,0); console.log('this: ', this); } myKlass.prototype.prop = 'myName is myKlass'; myKlass.prototype.method = function(){ console.log(this); } convertToAllowApply('myKlass'); var t1 = myKlass.apply(null, [1,2,3]); console.log('t1 is: ', t1); 
+1
Apr 03 '13 at 12:01
source share



All Articles