有没有一种简单的方法可以将全局变量转换为局部变量



假设我们有一个如下所示的函数:

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 + ';')

最新更新