Let's say we have a function that looks like this:
const fn = () => x;
This function should return the value of x
where x
is available in the global scope. Initially this is undefined
but if we define x
:
const x = 42;
Then we can expect fn
to return 42
.
Now let's say we wanted to render fn
as a string. In JavaScript we have toString
for this purpose. However let's also say we wanted to eventually execute fn
in a new context (i.e. using eval
) and so any global references it uses should be internalized either before or during our call to toString
.
How can we make x
a local variable whose value reflects the global value of x
at the time we convert fn
to a string? Assume we cannot know x
is named x
. That said we can assume the variables are contained in the same module.
Functions can access global variables and modify them. Modifying global variables in a function is considered poor programming practice. It is better to send a variable in as a parameter (or have it be returned in the 'return' statement).
Normally, when you create a variable inside a function, that variable is local, and can only be used inside that function. To create a global variable inside a function, you can use the global keyword.
Local variables can only be accessed within the function or module in which they are defined, in contrast to global variables, which can be used throughout the entire program. In Python, a Global variable can be defined using the global Keyword, also we can make changes to the variable in the local context.
It is usually not a good programming practice to give different variables the same names. If a global and a local variable with the same name are in scope, which means accessible, at the same time, your code can access only the local variable.
If you want lock certain variables while converting function to string, you have to pass that variables along the stringified function.
It could be implemented like this (written with types -- typescript notation)
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 stringified variables and funtion in place, where it was executed. This could be problem, you might redeclare some variable to new, unknown value, you must know the name of stringified function to be able to call it or it can produce an error, if you redeclare already declared const
variable.
To overcome that issue, you shall implement the stringified function as immediatelly executed anonymous function with its own scope, like
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 function and variables in separate scope and then it will assign that function to fn.name
constant. The variables will not polute the scope, where you eval
, it will just declare new fn.name
variable and this new variable will be set to deserialized function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With