3

So I have this code:

function fn(a){
  var f=(new Function("return a"));
  return f();
}
fn(7)//ReferenceError: a is not defined

Same problems with local variables:

function fn(){
  var a=7;
  var f=new Function("return a");
  return f();
}
fn(7)//ReferenceError: a is not defined

I want it to return a but the new function cant see a, it can only see global a

var a=1;
function fn(a){
  var f=(new Function("return a"));
  return f();
}
fn(7)//1

With the normal initialization the function can see the argument.

function fn(a){
  var f=function(){return a};
  return f();
}
fn(7)//7

I need to call the basic constructor in my project and can't use global variables. I know that i could solve this by giving arguments to the newly created function and call it with that like this:

function fn(a){
  var f=(new Function('a',"return a"));
  return f(a);
}
fn(7)//7

And also could use some parsing function and some stupidly long method to make incoming arguments reachable like this:

function parsargs(funct){
   //some parsing methodes giving back argument name list from funct.toString()
  return "['a','b']";//like this
}


function fn(a,b){
  var arrgstr,retfunc;

  arrgstr="";
  for(var i in arguments)
  {
    if(i<arguments.length-1)
      arrgstr+=arguments[i]+",";
    else
      arrgstr+=arguments[i];
  }
  //return arrgstr;
  retfunc="var f=new Function("+parsargs()+",'return b*a');return f("+arrgstr+")";
  return (new Function(retfunc))();
}
fn(7,4)//28

But there must be an easier way which reaches local variables and functions as well... Any suggestions?

PS: i am trying to replace eval() in the project

Here is a simplified version of my original problem: fiddle

The answer is NO...

szinter
  • 283
  • 1
  • 8

4 Answers4

2

Your exact question isn't clear but, supposing you can use arguments.callee (i.e. non strict mode) and that you want to be able to have any arguments name in fn, you "may" do this:

function fn(a,b){
  var strargs = arguments.callee.toString().match(/\(([^\)]*)\)/)[1];
  return (new Function(strargs.split(","),"return a+b")).apply(null,arguments);
}
console.log(fn(7, 3)) // 10

But I have a strong feeling this is a XY question and that we could have given a more useful answer knowing the real original problem to solve.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • Thanks for the answer, i added a fiddle where is a simplified version of the original code. I want to replace the eval() s to new Function(). Would you please look at it? You will might have some suggestions. – szinter May 05 '15 at 13:49
  • @szinter You could parse the string that you're currently evaling but the only clean and maintanable solution would be define a structural model of what you're currently evaling and pass an object instead of a free code. This model would of course totally depend of your application logic and realm. – Denys Séguret May 05 '15 at 13:51
  • This solution replaces a nightmarishly long if else based code(8 KLOC~) – szinter May 05 '15 at 13:57
  • @szinter Modelling complex constraints is always a complex task (and if it's if/else based it's obviously a bad model). But it's better than not modelling them. – Denys Séguret May 05 '15 at 13:58
1

The reason why this isn't working as easy as you would like is that JavaScript doesn't blindly put variables in a scope. Instead, it parses the function body (as good as it can) and determines which variables the code will need. It will then look up the variables in the outer scopes, create references for them and attach those references to the new function. That's what keeps variables from the outer scope accessible inside of the function when it eventually will be executed.

You want to build a function from a string body. JavaScript can't tell which variables the body will use and hence, it won't make those variables available.

To make it work for function arguments, use apply()::

function fn(a){
   var argref = arguments; // create reference to the arguments of fn() and hence to a or any other arguments
   var func = new Function("return arguments[0]");
   var wrapper = function() {
      return func.apply(null, argref);
   };
   return wrapper;
}

Note that you still can't reference arguments by name since the code never specifies what the names of the arguments of func are - JavaScript can't magically read you mind and do what you want. If you want to access the arguments by name, you need to tell the interpreter the names.

This question has some code how to determine the names from a function reference: How to get function parameter names/values dynamically from javascript

I don't see a way to make local variables available without passing them to fn() as arguments. Even if you used them inside of fn(), they would be out of scope when func() is eventually executed.

A simple solution would be to pass an object to fn():

function fn(conf) {
   var func = new Function('conf', "return conf.a");
   var wrapper = function(conf) {
       return func.apply(null, conf);
   };
   return wrapper;
}

fn({a:7});
Community
  • 1
  • 1
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • Thanks for the answer, i added a fiddle where is a simplified version of the original code. I want to replace the eval() s to new Function(). Would you please look at it? You will might have some suggestion. – szinter May 05 '15 at 13:46
  • Why are you trying to replace `eval()`? – Aaron Digulla May 06 '15 at 12:03
1

new Function doesn't create a closure context, but eval does.

Here's a low-tech way to build a function with an arbitrary evaluated body and access to the calling scope:

function f(a) {
  return eval("(function() { return a })")()
}

f(10) // 10
joews
  • 29,767
  • 10
  • 79
  • 91
  • yeah i forgot to tell that i am trying to replacing eval() in the project sry – szinter May 05 '15 at 12:56
  • What's the reason for removing it? – joews May 05 '15 at 12:56
  • Do you know that `new Function` will be faster? [this JSPerf](http://jsperf.com/eval-vs-new-function-constructor/9) suggests `eval` is faster (at least in the simple test cases). – joews May 05 '15 at 13:32
  • I found this perf: http://jsperf.com/eval-vs-new-function witch suggested new Function will be faster – szinter May 05 '15 at 13:40
  • yes i can see bigger numbers next to new Function in FF but lover numbers in chrome. You are kind of right but in the same time not :) – szinter May 05 '15 at 13:52
  • 1
    Interesting, and apologies for jumping to a conclusion :) – joews May 05 '15 at 14:01
1

You could call your new Function with a context that references the local variables that you need:

function f(a) {
  var b = 30;
  return new Function("return this.a + this.b").call({ a: a, b: b })
}

f(10) // 40
joews
  • 29,767
  • 10
  • 79
  • 91
  • Not directly answers the question but your previous answer pointed out the basic problem i have with that matter, so i ll accept this answer because you gave me a work around that i can use. – szinter May 07 '15 at 08:07