为什么使用JavaScript的eval函数是一个坏主意?

586

eval函数是一种强大且易于动态生成代码的方法,那么有哪些需要注意的地方呢?


100
不要使用eval()函数 by Simon Willison - http://24ways.org/2005/dont-be-eval - Brian Singh
6
如http://moduscreate.com/javascript-performance-tips-tricks/中所概述的- (new Function(str))()比eval(str)更具性能。这只是我的个人观点 :) - Grgur
3
显然,Chrome浏览器上的新功能(a)比eval(a)慢67%。 - what is sleep
3
我添加了一个静态函数,只是为了比较性能。http://jsperf.com/eval-vs-new-function/2 - Nepoxx
1
@Nepoxx,你的网站挂了。 - 9pfs
显示剩余2条评论
25个回答

6

它是一种可能的安全风险,具有不同的执行范围,并且效率相当低下,因为它创建了一个完全新的脚本环境来执行代码。在此处查看更多信息:eval

但是,适度使用它非常有用,可以添加很多好的功能。


5

除非您100%确定正在评估的代码来自可信源(通常是您自己的应用程序),否则这是暴露您的系统受到跨站脚本攻击的一种方法。


1
只有在服务器端的安全性很差的情况下,客户端的安全性才是无意义的。 - doubleOrt

5

只要你知道使用上下文,它并不一定那么糟糕。

如果您的应用程序使用eval()从一些JSON创建对象,该JSON来自于由您信任的服务器端代码创建的针对您自己网站的XMLHttpRequest,那么这可能不是问题。

不受信任的客户端JavaScript代码本身无法做太多事情。只要您要执行eval()的内容来自合理的来源,那么就没问题。


3
使用 eval 比仅解析 JSON 更慢,对吗? - Brendan Long
@Qix - 在我的浏览器(Chrome 53)上运行该测试显示evalparse稍快。 - Periata Breatta
@PeriataBreatta 嗯,奇怪。我不知道为什么会这样。然而,Chrome在某些运行时的特定领域从版本到版本获得奇怪的性能提升并不鲜见。 - Qix - MONICA WAS MISTREATED
这里是一个有点老的线程,但从我所阅读的来看——不是自己追溯过——JSON.parse在最终阶段实际上会对其输入进行评估。从效率上来说,需要更多的工作/时间。但从安全的角度考虑,为什么不只是解析呢?eval是一种很棒的工具。用它来处理没有其他方法的事情。要通过JSON传递函数,有一种方法可以避免使用eval。JSON.stringify中的第二个参数可让您设置一个回调函数进行检查。如果它是一个函数,则可以通过typeof检查。然后获取该函数的.toString()。如果搜索一下,就会发现有一些很好的文章。 - jdmayfield

4
如果您希望用户输入一些逻辑函数并进行AND和OR计算,则JavaScript eval函数非常适合。它可以接受两个字符串并eval(uate) string1 === string2等等。

你也可以使用 Function() {},但是在服务器上使用时要小心,除非你想让用户接管你的服务器哈哈哈。 - aoeu256

4

它大大降低了您对安全的信心。


4

如果你的代码中使用了eval(),请记住口号“eval()是邪恶的”。

这个函数接受一个任意字符串并将其作为JavaScript代码执行。当要执行的代码在先就已知(不是在运行时确定)时,没有理由使用eval()。 如果代码是在运行时动态生成的,通常有更好的方法可以实现目标而不使用eval()。 例如,只需使用方括号表示法访问动态属性就更好、更简单:

// antipattern
var property = "name";
alert(eval("obj." + property));

// preferred
var property = "name";
alert(obj[property]);

使用eval()也存在安全隐患,因为您可能会执行已被篡改的代码(例如来自网络)。当处理来自Ajax请求的JSON响应时,这是常见的反模式。在这些情况下,最好使用浏览器内置方法解析JSON响应,以确保其安全且有效。对于不支持本地JSON.parse()的浏览器,可以使用来自JSON.org的库。
还要记住,将字符串传递给setInterval()setTimeout()Function()构造函数,在大多数情况下类似于使用eval(),因此应避免使用。
在幕后,JavaScript仍然需要评估和执行您传递的编程代码字符串:
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);

// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
myFunc(1, 2, 3);
}, 1000);

使用新的Function()构造函数类似于eval(),需要小心对待。它可能是一个强大的结构,但经常被误用。 如果你一定要使用eval(),可以考虑使用new Function()代替。

存在一些潜在的好处,因为在new Function()中运行的代码将在局部函数作用域中运行, 因此在被评估的代码中用var定义的任何变量都不会自动变成全局变量。

另一种防止自动全局变量的方法是将eval()调用包装在立即函数中。


你能否建议我如何在不使用eval的情况下评估函数本地动态变量名称?Eval(以及类似的)函数在大多数包含它们的语言中都是最后的手段,但有时是必要的。在获取动态变量名称的情况下,是否有任何更安全的解决方案?无论如何,JavaScript本身并不是真正的安全(服务器端显然是主要防御)。如果您感兴趣,这是我的eval用例,我很想改变它:https://dev59.com/HV4b5IYBdhLWcg3waA5P#48294208 - Regular Jo

3

编辑:如Benjie的评论所示,这在chrome v108中似乎不再是问题了,看起来chrome现在可以处理evaled脚本的垃圾回收。

垃圾回收

浏览器的垃圾回收机制无法确定eval执行的代码是否可以从内存中删除,因此它会将其保留在内存中,直到页面重新加载。 如果用户只短暂地访问您的网页,则影响不大,但对于Web应用程序而言可能会成为问题。

以下是演示该问题的脚本:

https://jsfiddle.net/CynderRnAsh/qux1osnw/

document.getElementById("evalLeak").onclick = (e) => {
  for(let x = 0; x < 100; x++) {
    eval(x.toString());
  }
};

像上面的代码一样简单,会导致一小部分内存存储到应用程序关闭时。 如果作为eval的脚本是一个庞大的函数,并且按间隔调用,则情况更糟。


1
在运行与Node相同的V8 JS引擎的Chrome v108中似乎不是这种情况。虽然eval版本似乎会更快地攀升,但它花费约80秒才达到10MB,然后进行垃圾回收并继续运行。点击左上角的垃圾桶图标(“回收垃圾”)也会将内存使用情况重置为原始值。没有任何泄漏现象。 - Benjie
1
刚刚测试了一下,你是正确的,看起来这可能不再是问题了。谢谢@Benjie。 - J D

2

并不是总是一个坏主意。以代码生成为例。我最近编写了一个名为Hyperbars的库,它连接了virtual-domhandlebars。它通过解析handlebars模板并将其转换为hyperscript来实现这一点,随后由virtual-dom使用。首先将hyperscript作为字符串生成,然后在返回之前,使用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()的性能在这种情况下并不成问题,因为您只需要解释生成的字符串一次,然后多次重复使用可执行输出。
如果您好奇代码生成是如何实现的,可以在此处查看:这里

2

eval()是一种非常强大的函数,可以用来执行JS语句或者评估表达式。但是问题不在于eval()的使用方式,假设你要运行的字符串受到了恶意方的影响,那么最终你将会运行恶意代码。拥有力量就需要承担责任,所以如果你在使用eval(),请明智地使用它。

虽然这与eval()函数关系不大,但是这篇文章提供了相当不错的信息:http://blogs.popart.com/2009/07/javascript-injection-attacks/ 如果你想了解eval()的基本知识,请查看这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval


2
JavaScript引擎在编译阶段执行了一些性能优化。其中一些优化可以归结为在词法分析代码时能够基本静态地分析代码,并预先确定所有变量和函数声明的位置,以便在执行期间解析标识符时需要更少的努力。
但是,如果引擎在代码中找到一个eval(..),它实际上必须假定其对标识符位置的所有认知可能无效,因为它不能在词法分析时确切地知道您可能传递给eval(..)来修改词法作用域的所有代码,或者您可能传递给with以创建要查询的新词法作用域对象的内容。
换句话说,在悲观意义上,如果存在eval(..),则它将不执行大多数这些优化,因此根本不执行这些优化。
这就解释了一切。
参考资料:

https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#eval

https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#performance


没有任何 JavaScript 引擎可以百分之百地保证在代码中找到并执行 eval。因此,必须随时准备好应对它。 - Jack G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接