我正在编写一些JavaScript代码来解析用户输入的函数(用于类似电子表格的功能)。解析完公式后,我可以将其转换为JavaScript并在其上运行eval()以产生结果。

然而,我总是避免使用eval(),因为它是邪恶的(而且,无论正确与否,我一直认为它在JavaScript中更邪恶,因为要计算的代码可能会被用户更改)。

那么,什么时候可以使用它呢?


当前回答

代码生成。我最近写了一个叫做Hyperbars的库,它在虚拟世界和把手之间架起了桥梁。它通过解析句柄模板并将其转换为超脚本来实现这一点。超脚本首先作为字符串生成,在返回它之前,eval()将其转换为可执行代码。我发现eval()在这种特殊情况下与邪恶完全相反。

基本上从

<div>
    {{#each names}}
        <span>{{this}}</span>
    {{/each}}
</div>

这个

(function (state) {
    var Runtime = Hyperbars.Runtime;
    var context = state;
    return h('div', {}, [Runtime.each(context['names'], context, function (context, parent, options) {
        return [h('span', {}, [options['@index'], context])]
    })])
}.bind({}))

eval()的性能在这种情况下也不是问题,因为您只需要解释一次生成的字符串,然后多次重用可执行输出。

如果您好奇的话,您可以看到代码生成是如何实现的。

其他回答

什么时候JavaScript的eval()不是邪恶的?

我总是不鼓励使用eval。几乎总是有更干净和可维护的解决方案可用。Eval甚至对于JSON解析也不需要。Eval增加了维护的难度。道格拉斯·克罗克福德(Douglas Crockford)这样的大师并不赞同这一点,这不无道理。

但我发现了一个应该使用它的例子:

当你需要传递表达式时。

例如,我有一个函数,为我构造了一个通用的google.maps.ImageMapType对象,但我需要告诉它配方,它应该如何从缩放和坐标参数构造tile URL:

my_func({
    name: "OSM",
    tileURLexpr: '"http://tile.openstreetmap.org/"+b+"/"+a.x+"/"+a.y+".png"',
    ...
});

function my_func(opts)
{
    return new google.maps.ImageMapType({
        getTileUrl: function (coord, zoom) {
            var b = zoom;
            var a = coord;
            return eval(opts.tileURLexpr);
        },
        ....
    });
}

底线

如果你创建或净化了你评估的代码,它永远不会是邪恶的。

略为详细

如果在服务器上使用不是由开发人员创建或未经过开发人员处理的客户端提交的输入运行Eval是邪恶的。

如果在客户端上运行Eval,即使使用客户端制作的未经处理的输入,它也不是邪恶的。

显然,您应该始终清理输入,以便对代码使用的内容有一定的控制。

推理

客户端可以运行任何他们想要的代码,即使开发人员没有编写这些代码;这不仅适用于被求值的对象,而且适用于对eval本身的调用。

我的使用eval: import的例子。

通常的做法。

var components = require('components');
var Button = components.Button;
var ComboBox = components.ComboBox;
var CheckBox = components.CheckBox;
...
// That quickly gets very boring

但是在eval和一个小helper函数的帮助下,它得到了更好的外观:

var components = require('components');
eval(importable('components', 'Button', 'ComboBox', 'CheckBox', ...));

Importable可能看起来像这样(此版本不支持导入具体成员)。

function importable(path) {
    var name;
    var pkg = eval(path);
    var result = '\n';

    for (name in pkg) {
        result += 'if (name !== undefined) throw "import error: name already exists";\n'.replace(/name/g, name);
    }

    for (name in pkg) {
        result += 'var name = path.name;\n'.replace(/name/g, name).replace('path', path);
    }
    return result;
}

由于还没有人提到它,让我补充一下eval对于Webassembly-Javascript互操作非常有用。虽然在您的页面中包含WASM代码可以直接调用的预先制作的脚本当然是理想的,但有时这是不可行的,您需要从Webassembly语言(如c#)传入动态Javascript来真正完成您需要做的事情。

在这种情况下,它也是安全的,因为您可以完全控制传入的内容。好吧,我应该说,这并不比使用c#编写SQL语句更安全,也就是说,无论何时使用用户提供的数据来生成脚本,都需要谨慎地执行(正确转义字符串等)。但在这种情况下,它在互操作场景中有一个明确的位置,远非“邪恶”。

我认为评价被证明是正确的情况将是罕见的。”你更有可能认为它是合理的,而不是在实际合理的情况下使用它。

安全问题最为人所知。但也要注意JavaScript使用JIT编译,这与eval的工作效果很差。Eval有点像编译器的黑匣子,JavaScript需要能够提前(在某种程度上)预测代码,以便安全正确地应用性能优化和范围。在某些情况下,性能影响甚至会影响eval之外的其他代码。

如果你想了解更多: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval