Is there an easy way to turn a global var into a local var?

Let's say we have a function that looks like this:

const fn = () => x; 

This function should return x , where x available in the global scope. This is initially undefined , but if we define x :

 const x = 42; 

Then we can expect fn return 42 .

Now suppose we wanted to make fn as a string. In JavaScript, we have toString for this. However, suppose we wanted to end up executing fn in a new context (i.e., using eval ), and therefore any global references that it uses should be internalized before or during our call toString .

How can we make x local variable whose value reflects the global value of x during the conversion of fn to a string? Suppose we cannot know that x has the name x . In this case, we can assume that the variables are contained in one module.

+7
javascript
source share
7 answers

We cannot know that x has the name x . This is the central part of this puzzle and is therefore highlighted in the original question. Although it would be nice if we had a simpler solution, it seems that the correct answer here comes down to implementing some kind of analyzer or AST bypass.

Why is this necessary? Although we can assume that x lives in the module as global (it necessarily separates between functions), we cannot assume that it has a known name. So, we need to somehow extract x (or all global variables indeed) from our module, and then provide it as a context when we end up eval .

NB: providing known variables as context is trivial. The few answers here seem to suggest a complex problem, but actually it is pretty easy to do with eval ; just add context as a string.

So what is the correct answer here? If we used AST ( Acorn , maybe, for example, a viable starting point), we could study the module and programmatically extract all the globals in it. This includes x or any other variable that can be shared between our functions; we can even test functions to determine which variables are needed to execute them.

Again, the hope of asking this question first was to drive away a simpler solution or disclose the prior art that could be adapted to our needs. Ultimately, my answer and the answer that I accept come down to the non-trivial task of parsing and extracting globals from a JavaScript module; does not seem to be a simple way. I think this is a fair answer, if not practical for us, to implement today. (We will, however, consider this later as our project grows.)

0
source share

If you want to block certain variables when converting a function to a string, you need to pass these variables along the string function.

It can be implemented as follows (written with type - typescript)

 const prepareForEval = (fn: Function, variablesToLock: { [varName: string]: any }): string => { const stringifiedVariables = Object.keys(variablesToLock) .map(varName => `var ${varName}=${JSON.stringify(variablesToLock[varName])};`); return stringifiedVariables.join("") + fn.toString(); } 

Then use it like this:

 const stringifiedFunction = prepareForEval(someFunction, { x: x, y: y }) // you can even simplify declaration of object, in ES6 you simply write const stringifiedFunction = prepareForEval(someFunction, { x, y }) // all variables you write into curly braces will be stringified // and therefor "locked" in time you call prepareForEval() 

Any eval will declare string variables and funtion in the place where it was executed. This can be a problem, you can redefine a variable to a new unknown value, you must know the name of the gated function in order to be able to call it, or it can cause an error if you re-declare an already declared const variable.

To overcome this problem, you must implement a gated function as a directly executed anonymous function with its own scope, e.g.

 const prepareForEval = (fn: Function, variablesToLock: { [varName: string]: any }): string => { const stringifiedVariables = Object.keys(variablesToLock) .map(varName => `var ${varName}=${JSON.stringify(variablesToLock[varName])};`); return ` var ${fn.name} = (function() { ${stringifiedVariables.join("")} return ${fn.toString()}; )(); `; } 

this modification will declare the function and variables in a separate scope, and then assign this function to the constant fn.name . The variables will not change the scope where you are eval , it will simply declare a new variable fn.name , and this new variable will be set to a deserialized function.

+1
source share

You can use the operator OR || to combine the current value of x in fn.toString() call

 const fn = () => x; const x = 42; const _fn = `${fn.toString()} || ${x}`; console.log(_fn, eval(_fn)()); 
0
source share

Global variables can be made local (private) with closure. w3Schools

 function myFunction() { var a = 4; return a * a; } 
0
source share

Thanks guest271314 , now I see what you want.

This is his code, slightly improved:

 const stringifiedFn = ` (function() { const _a = (${fn.toString()})(); return _a !== undefined ? _a : ${JSON.stringify(fn())}; })(); `; 

this code will execute fn in the context where you are eval , and if fn in this context returns undefined , it returns the result of fn in the context where it was wall-mounted.

All loans are sent to guest271314

0
source share

Do you mean this? only the answer can send the code, so I use the answer

 var x = 42 function fn() { return x } (() => { var x = 56 var localFn = eval('"use strict";(' + fn.toString()+')') console.log(localFn) console.log(localFn()) })() 
  • why rename to localFn , if you use var fn=xx in this area, external fn never exists!
  • in nodejs? refer nodejs vm
  • conveying context? you cannot save js context unless you keep your own scale, e.g. angularjs
-one
source share

If you are already “going there” using eval () to execute fn () in a new context, then why not define the function itself using eval ()?

eval('const fn = () => ' + x + ';')

-one
source share

All Articles