34

I need to create a function with variable number of parameters using new Function() constructor. Something like this:

args = ['a', 'b'];
body = 'return(a + b);';

myFunc = new Function(args, body);

Is it possible to do it without eval()?


Thank you very much, guys! Actually, a+b was not my primary concern. I'm working on a code which would process and expand templates and I needed to pass unknown (and variable) number of arguments into the function so that they would be introduced as local variables.

For example, if a template contains:

<span> =a </span> 

I need to output the value of parameter a. That is, if user declared expanding function as

var expand = tplCompile('template', a, b, c) 

and then calls

expand(4, 2, 1) 

I need to substitute =a with 4. And yes, I'm well aware than Function is similar to eval() and runs very slow but I don't have any other choice.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
mtelis
  • 654
  • 1
  • 6
  • 10
  • Why not just loop through the array? There's probably a better way to go about whatever you're trying to do. – Nick Craver Nov 15 '10 at 10:53
  • I fear eval can't be avoided as you try to evaluate pure text as code. – Shadow The GPT Wizard Nov 15 '10 at 11:02
  • There are better ways to do this. If you absolutely must use `new Function()`, that sounds like a homework assignment. In that case, the instructor is teaching you how do to things the wrong way. – darioo Nov 15 '10 at 11:13

11 Answers11

58

You can do this using apply():

args = ['a', 'b', 'return(a + b);'];
myFunc = Function.apply(null, args);

Without the new operator, Function gives exactly the same result. You can use array functions like push(), unshift() or splice() to modify the array before passing it to apply.

You can also just pass a comma-separated string of arguments to Function:

args = 'a, b';
body = 'return(a + b);';

myFunc = new Function(args, body);

On a side note, are you aware of the arguments object? It allows you to get all the arguments passed into a function using array-style bracket notation:

myFunc = function () {
    var total = 0;

    for (var i=0; i < arguments.length; i++)
        total += arguments[i];

    return total;
}

myFunc(a, b);

This would be more efficient than using the Function constructor, and is probably a much more appropriate method of achieving what you need.

Andy E
  • 338,112
  • 86
  • 474
  • 445
  • You suggestion's just perfect! The following works like a charm: args = 'a, b'; body = 'return(a + b);'; myFunc = new Function(args, body); – mtelis Nov 16 '10 at 09:29
  • @mtelis: great stuff, I'm glad it helped you :-) You can mark an answer as accepted by clicking the check mark just below the voting arrows. And welcome to Stack Overflow! – Andy E Nov 16 '10 at 09:36
  • Won't the `this` variable be different in `myFunc` if you don't use new operator? – Timo Huovinen Nov 24 '12 at 12:25
  • @Timo: In this case, no. Like most built-in constructors, omitting the `new` operator makes no behavioural difference. We're not using the `this` object here anyway. – Andy E Nov 24 '12 at 12:34
  • @AndyE thanks for the quick reply, I didn't realize that `new Function` was the actual function and not some placeholder name... whoops – Timo Huovinen Nov 24 '12 at 13:19
8

@AndyE's answer is correct if the constructor doesn't care whether you use the new keyword or not. Some functions are not as forgiving.

If you find yourself in a scenario where you need to use the new keyword and you need to send a variable number of arguments to the function, you can use this

function Foo() {
  this.numbers = [].slice.apply(arguments);
};


var args = [1,2,3,4,5]; // however many you want
var f = Object.create(Foo.prototype);
Foo.apply(f, args);

f.numbers;          // [1,2,3,4,5]
f instanceof Foo;   // true
f.constructor.name; // "Foo"

ES6 and beyond!

// yup, that easy
function Foo (...numbers) {
  this.numbers = numbers
}

// use Reflect.construct to call Foo constructor
const f =
  Reflect.construct (Foo, [1, 2, 3, 4, 5])

// everything else works
console.log (f.numbers)          // [1,2,3,4,5]
console.log (f instanceof Foo)   // true
console.log (f.constructor.name) // "Foo"
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    This answer deals with arbitrary constructors. However, the question was specifically about the `Function` constructor. The function constructor does not have to be used with `new` at all and so it’s sufficient to just use `Function.apply(null, args)`. – Raphael Schweikert Jul 07 '16 at 11:04
6

You can do this:

let args = '...args'
let body = 'let [a, b] = args;return a + b'

myFunc = new Function(args, body);
console.log(myFunc(1, 2)) //3
Thiago Lagden
  • 161
  • 1
  • 4
1

Maybe you want an annoymous function to call an arbitary function.

// user string function
var userFunction = 'function x(...args) { return args.length}';

Wrap it

var annoyFn = Function('return function x(...args) { return args.length}')()
// now call it
annoyFn(args)
Tearf001
  • 95
  • 7
0

If you're just wanting a sum(...) function:

function sum(list) {
    var total = 0, nums;
    if (arguments.length === 1 && list instanceof Array) {
        nums = list;
    } else {
        nums = arguments;
    }
    for (var i=0; i < nums.length; i++) {
        total += nums[i];
    }
    return total;
}

Then,

sum() === 0;
sum(1) === 1;
sum([1, 2]) === 3;
sum(1, 2, 3) === 6;
sum([-17, 93, 2, -841]) === -763;

If you want more, could you please provide more detail? It's rather difficult to say how you can do something if you don't know what you're trying to do.

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
0

There's a few different ways you could write that.

// assign normally
var ab = ['a','b'].join('');
alert(ab);
// assign with anonymous self-evaluating function
var cd = (function(c) {return c.join("");})(['c','d']);
alert(cd);
// assign with function declaration
function efFunc(c){return c.join("");}
var efArray = ['e','f'];
var ef = efFunc(efArray);
alert(ef);
// assign with function by name
var doFunc = function(a,b) {return window[b](a);}
var ghArray = ['g','h'];
var ghFunc = function(c){return c.join("");}
var gh = doFunc(ghArray,'ghFunc');
alert(gh);
// assign with Class and lookup table
var Function_ = function(a,b) {
  this.val = '';
  this.body = b.substr(0,b.indexOf('('));
  this.args = b.substr(b.indexOf('(')+1,b.lastIndexOf(')')-b.indexOf('(')-1);
  switch (this.body) {
    case "return": 
      switch (this.args) {
        case "a + b": this.val = a.join(''); break;
      }
    break;
  }
} 
var args = ['i', 'j'];
var body = 'return(a + b);';
var ij = new Function_(args, body);
alert(ij.val);
WolfRevoKcats
  • 294
  • 1
  • 5
  • But that's just equivalent to `var ab = (function(c) {...})(['a','b'])`! – Chris Morgan Nov 15 '10 at 11:13
  • @Chris Morgan, true true. I was thinking he wanted to map the functions to variables rather than put them into one function expression and evaluate it. – WolfRevoKcats Nov 15 '10 at 11:26
  • Thank you very much, guys! Actually, a+b was not my primary concern. I'm working on a code which would process and expand templates and I needed to pass unknown (and variable) number of arguments into the function so that they would be introduced as local variables. For example, if a template contains: – mtelis Nov 15 '10 at 21:59
  • =a I need to output the value of parameter "a". That is, if user declared expanding function as var expand = tplCompile('template', a, b, c) t – mtelis Nov 15 '10 at 22:02
0

A new feature introduced in ES5 is the reduce method of arrays. You can use it to sum numbers, and it is possible to use the feature in older browsers with some compatibility code.

PleaseStand
  • 31,641
  • 6
  • 68
  • 95
-1
function construct(){
         this.subFunction=function(a,b){
         ...  
         }
}
var globalVar=new construct();   

vs.

var globalVar=new function (){
              this.subFunction=function(a,b){
              ...
              }
}

I prefer the second version if there are sub functions.

B.F.
  • 477
  • 6
  • 9
-1
new Function(...)

Declaring function in this way causes the function not to be compiled, and is potentially slower than the other ways of declaring functions.

Let is examine it with JSLitmus and run a small test script:

<script src="JSLitmus.js"></script>
<script>

JSLitmus.test("new Function ... ", function() { 
    return new Function("for(var i=0; i<100; i++) {}"); 
});

JSLitmus.test("function() ...", function() { 
       return (function() { for(var i=0; i<100; i++) {}  });
});

</script>

What I did above is create a function expression and function constructor performing same operation. The result is as follows:

FireFox Performance Result

FireFox Performance Result

IE Performance Result

IE Performance Result

Based on facts I recommend to use function expression instead of function constructor

var a = function() {
 var result = 0;
 for(var index=0; index < arguments.length; index++) {
  result += arguments[index];
 }
 return result;
 }
alert(a(1,3));
Ramiz Uddin
  • 4,249
  • 4
  • 40
  • 72
  • 1
    Could you link the resource where you took that sentence about performance from? – Alberto Zaccagni Nov 15 '10 at 11:30
  • 14
    the citation is *wrong*, there are alot of myths about this. The function *is compiled* just after the moment you call the [`Function` constructor](http://sideshowbarker.github.com/es5-spec/#x15.3.2.1). After that it is just like *any other user-defined function*, no performance penalties, the *slow part* may be the function creation, but not the function itself after its creation... – Christian C. Salvadó Nov 15 '10 at 14:52
  • 15
    of course, the *function creation* is slower, the engine will need to re-parse and evaluate the `FormalParameterList` and `FunctionBody` at runtime, and occurs *thousands of times* within your test. What I'm trying to tell you is that the function, *once it is created* by the `Function` constructor is "compiled" and it will have a performance equivalent to a function created by other means. Modify your test to check *invocation*, *not creation*, and you will see really similar results between the two. See [this test](http://j.mp/crC9xY) and this [question](http://j.mp/ddZu3G) Cheers :) – Christian C. Salvadó Nov 16 '10 at 02:43
-1

the b.apply(null, arguments) does not work properly when b inherits a prototype, because 'new' being omitted, the base constructor is not invoked.

  • Is this supposed to be a comment on another answer? It doesn't seem to have anything to do with the question. – Bergi Apr 08 '17 at 19:03
  • This question asks specifically for the case `b === Function`, where `apply` does indeed work (it doesn't need to be called as a constructor). For the general case, see [here](http://stackoverflow.com/q/1606797/1048572). – Bergi Apr 08 '17 at 19:05
  • it is indeed a comment to @Andy E, but SO won't let me comment directly – user2969819 Apr 09 '17 at 22:17
  • Maybe I did not understand the question accurately, it just so happens that I was looking for a solution to a similar problem: pass arguments through a call chain, up to a constructor. – user2969819 Apr 09 '17 at 22:24
-1

In this sample i used lodash:

function _evalExp(exp, scope) {
  const k = [null].concat(_.keys(scope));
  k.push('return '+exp);
  const args = _.map(_.keys(scope), function(a) {return scope[a];});
  const func = new (Function.prototype.bind.apply(Function, k));
  return func.apply(func, args);
}

_evalExp('a+b+c', {a:10, b:20, c:30});
Leo
  • 1
  • 1