假设我们有一个如下所示的函数:
const fn = () => x;
此函数应返回x
的值,其中x
在全局作用域中可用。最初这是undefined
,但如果我们定义x
:
const x = 42;
那么我们可以期望fn
返回42
。
现在假设我们想要将fn
呈现为一个字符串。在JavaScript中,我们有用于此目的的toString
。然而,我们也要说,我们希望最终在新的上下文中执行fn
(即使用eval
),因此它使用的任何全局引用都应该在调用toString
之前或期间内化。
我们如何使x
成为一个局部变量,其值反映了将fn
转换为字符串时x
的全局值假设我们不知道x
被命名为x
也就是说,我们可以假设变量包含在同一个模块中。
如果您想在将函数转换为字符串时锁定某些变量,则必须将这些变量与字符串化函数一起传递。
它可以这样实现(用类型编写——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();
}
然后像这个一样使用它
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()
任何eval
都将在执行字符串化变量和函数的位置声明。这可能是个问题,您可能会将某个变量重新声明为新的未知值,您必须知道字符串化函数的名称才能调用它,或者如果您重新声明了已声明的const
变量,它可能会产生错误。
为了克服这个问题,您应该将字符串化函数实现为具有自己作用域的即时执行匿名函数,如
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()};
)();
`;
}
此修改将在单独的作用域中声明函数和变量,然后将该函数分配给fn.name
常量。变量不会改变作用域,在eval
中,它只会声明新的fn.name
变量,并且这个新变量将被设置为反序列化函数。
我们无法知道x
的名称为x
。这是这个谜题的核心部分,因此在最初的问题中更加大胆。虽然如果我们有一个更简单的解决方案会很好,但在这里,实现某种解析器或AST遍历似乎是一个合适的答案。
为什么这是必要的?虽然我们可以假设x
作为全局存在于模块中(它必须在函数之间共享),但我们不能假设它有一个已知的名称。因此,我们需要某种方式从模块中提取x
(或者实际上是所有全局),然后在最终eval
时将其作为上下文提供。
注意:提供已知的变量作为上下文是微不足道的。这里的几个答案似乎认为这是一个难题,但事实上,使用eval
很容易做到;只需将上下文前置为字符串。
那么这里的正确答案是什么呢?如果我们使用AST(例如,Acorn可能是一个可行的起点),我们可以检查模块并以编程方式提取其中的所有全局变量。这包括x
或我们的函数之间可能共享的任何其他变量;我们甚至可以检查函数,以确定执行这些函数需要哪些变量。
同样,最初提出这个问题的希望是提取一个更简单的解决方案,或者揭示可能适合我们需求的现有技术。最终,我的答案和我接受的答案归结为从JavaScript模块解析和提取全局变量这一重要任务;似乎没有简单的方法。我认为这是一个公平的答案,如果不是我们今天要执行的实际答案的话。(不过,随着项目的发展,我们稍后将对此进行处理。)
您可以使用OR
运算符||
将x
的当前值连接到fn.toString()
调用
const fn = () => x;
const x = 42;
const _fn = `${fn.toString()} || ${x}`;
console.log(_fn, eval(_fn)());
全局变量可以通过闭包变为本地(私有)变量。w3学校
function myFunction() {
var a = 4;
return a * a;
}
感谢留言271314,我现在明白您想要什么了。
这是他的代码,只是略有改进:
const stringifiedFn = `
(function() {
const _a = (${fn.toString()})();
return _a !== undefined ? _a : ${JSON.stringify(fn())};
})();
`;
此代码将在上下文中执行fn
,在上下文中为eval
,如果该上下文中的fn
返回undefined
,则在上下文中返回fn
的输出,在上下文中将其字符串化。
所有信用都将转到访客271314
你是这个意思吗?唯一的答案可以张贴代码,所以我使用答案
var x = 42
function fn() {
return x
}
(() => {
var x = 56
var localFn = eval('"use strict";(' + fn.toString()+')')
console.log(localFn)
console.log(localFn())
})()
- 为什么重命名为
localFn
,如果在此范围中使用var fn=xx
,则外部fn永远不存在 - 在nodejs中?参考nodejs-vm
- 传递上下文?除非像angularjs一样维护自己的作用域,否则无法保存js上下文
如果您已经在新的上下文中使用eval()来执行fn(),那么为什么不使用eval定义函数本身呢?
eval('const fn = () => ' + x + ';')