10
var foo = (function(){
  var x = "bar";
  return function(){
    console.log(x);
  };
})();

console.log(foo.toString()); // function() {console.log(x);}
(foo)(); // 'bar'
eval('(' + foo.toString()+')()')); // error: x is undefined

Is there a technique for resolving (modifying) a function, so references from outer scope become local references, like:

function() {console.log(x);}

becomes:

function() {console.log("bar");}

The function can now be stringified and transported across a network and executed in another runtime.

Maybe one could parse the function to an Abstract Syntax Tree and then modify it? The reference will always be out of scope (not available), right?

The objective:

I am serializing a filter function from a node runtime to a postgresql plv8 runtime. Right now the filter function has interface: dbClient.filter((row, age) => row.age > age), ageFromOuterScope).then(matches => ...)

I want interface dbClient.filter((row) => row.age > age)).then(matches => ...), where age is a reference from outer scope.

Update:

I can only imagine one solution. Analyze the function, detect references to variables outside the function, and then rewrite the original function:

function(row) {
   return row.age > age
}

To:

function(row, age) {
  return row.age > age
}

Detected variables should also be added to a string that represent an array, like:

var arrayString = '[age]'

And then eval the string:

var functionArgs = eval(arrayString)

And finally:

dbClient.filter(modifiedFunction, ...functionArgs).then(matches => ...)
Jacob
  • 1,642
  • 2
  • 15
  • 27
  • Most of the time such variables are modified at run time, so such conversions would change the code behavior. – Alexander O'Mara Oct 11 '15 at 04:42
  • 1
    Two comments: One, this is off topic for Stack Overflow since it's requesting a library. Two, this isn't how scoping works in Javascript, nor should it, which is why it doesn't work for you. Since it doesn't work in this way, how would a technique properly predict how to make this decision? This in turn makes the question to broad for Stack Overflow. – David L Oct 11 '15 at 04:42
  • I modified the question. How about using AST? – Jacob Oct 11 '15 at 04:47
  • Sure, that might work. If you had tried an AST approach and run into a difficulty, that would have made the question on topic for SO. As it still stands, it is off topic. – David L Oct 11 '15 at 04:48
  • Now i think the question is okay – Jacob Oct 11 '15 at 04:52
  • I think the answer is "No, there is not such a technique". – jfriend00 Oct 11 '15 at 05:16
  • With an abstract syntax tree you would get the value of the outer scope variable at parse time. Run-time values are a different matter. I concur, this is not possible. Serializing/exporting functions that depend on outer-scope variables is an exercise in futility. I smell an XY-problem. What are you trying to achieve? – Tomalak Oct 11 '15 at 06:18
  • 1
    I am serializing a filter function from a node runtime to a postgresql plv8 runtime. Right now the filter function has interface: dbClient.filter((row, age) => row.age > age), ageFromOuterScope).then(matches => ...). I want interface dbClient.filter((row) => row.age > age)).then(matches => ...), where age is a reference from outer scope. – Jacob Oct 11 '15 at 07:29
  • Related : http://stackoverflow.com/questions/4670805/javascript-eval-on-global-scope – nha Oct 16 '15 at 20:07
  • Thank you @nha. I'm not sure how it is related. Is it possible you could elaborate? – Jacob Oct 16 '15 at 20:27

3 Answers3

1

To expose the private variable outside the scope you need another function within the scope that rewrites the method description returned by toString. Then you use that function instead of toString to retrieve the method description.

var foo = (function(){
  var x = "bar";

  var f = function(){
    console.log(x);
  };

  f.decl = function() {
      return f.toString().replace("(x)", "(\""+x+"\")");
  }

  return f;

})();

console.log(foo.decl()); // function() {console.log("bar");}

eval("("+foo.decl()+")()"); // bar
Fluster
  • 133
  • 1
  • 9
0

I ran your top codebox's foo through Google's Closure Compiler, and it gave me this:

var foo=function(){return function(){console.log("bar")}}();foo;

not EXACTLY what you want, but you can get what you want from there using eval() and/or toString() as you're been tinkering with already.

I don't know how robust this is, and it makes other code mangles, but for the simple kind of functions you show, it does seem to consistently inline non-repeated primitives appearing in code.

dandavis
  • 16,370
  • 5
  • 40
  • 36
0

You can bind the x to the function object itself.

var foo = (function(){
  var x = "bar";
  return function(){
    this.x = x;
    console.log(this.x);
  };
})();

(foo)() // 'bar'

console.log(foo.toString()); // 'function() { this.x = x; console.log(this.x) }'

eval('(' + foo.toString()+')()'); // 'bar' 
Downhillski
  • 2,555
  • 2
  • 27
  • 39
  • When you do `(foo)()`, the `x` is going to be attached to the `window` object because of `this.x = x`. That's the reason why it still works when you call eval with foo.toString(), because `x` has "bar" value already preset. If you try to run the eval in a different global scope, it will fail – Balazs Nemeth Aug 21 '18 at 11:04