562

eval 函数是一种强大而简单的动态生成代码的方法,那么有哪些注意事项呢?

4

25 回答 25

403
  1. 不正确使用eval会打开您的代码进行注入攻击

  2. 调试可能更具挑战性(没有行号等)

  3. eval'd 代码执行速度较慢(没有机会编译/缓存 eval'd 代码)

编辑:正如@Jeff Walden 在评论中指出的那样,今天的#3 不如 2008 年那么真实。但是,虽然可能会发生一些编译脚本的缓存,但这将仅限于 eval'd 重复且未修改的脚本。更有可能的情况是,您正在评估每次都经过轻微修改的脚本,因此无法缓存。我们只是说一些 eval'd 代码执行得更慢。

于 2008-09-17T19:17:35.207 回答
352

eval 并不总是邪恶的。有时它是完全合适的。

然而,eval 目前和历史上都被不知道自己在做什么的人大量过度使用。不幸的是,这包括编写 JavaScript 教程的人,在某些情况下,这确实会产生安全后果 - 或者更常见的是简单的错误。所以我们越能在 eval 上打一个问号就越好。每当你使用 eval 时,你都需要彻底检查你在做什么,因为你可能会以一种更好、更安全、更清洁的方式来做这件事。

举一个非常典型的例子,使用存储在变量“potato”中的 id 来设置元素的颜色:

eval('document.' + potato + '.style.color = "red"');

如果上述代码的作者对 JavaScript 对象如何工作的基础知识有所了解,他们就会意识到可以使用方括号代替文字点名,从而无需 eval:

document[potato].style.color = 'red';

...这更容易阅读并且潜在的错误更少。

(但是,一个/真的/知道他们在做什么的人会说:

document.getElementById(potato).style.color = 'red';

这比直接从文档对象中访问 DOM 元素的狡猾的老技巧更可靠。)

于 2008-09-17T20:27:26.350 回答
38

我相信这是因为它可以从字符串执行任何 JavaScript 函数。使用它使人们更容易将恶意代码注入应用程序。

于 2008-09-17T19:11:45.453 回答
28

想到两点:

  1. 安全性(但只要您自己生成要评估的字符串,这可能不是问题)

  2. 性能:在要执行的代码未知之前,无法进行优化。(关于 javascript 和性能,当然是Steve Yegge 的演讲

于 2008-09-17T19:20:52.470 回答
25

如果您传递 eval 用户输入,这通常只是一个问题。

于 2008-09-17T19:12:59.740 回答
22

将用户输入传递给 eval() 存在安全风险,而且每次调用 eval() 都会创建一个新的 JavaScript 解释器实例。这可能是一种资源消耗。

于 2008-09-17T19:28:20.360 回答
16

主要是,维护和调试要困难得多。这就像一个goto. 您可以使用它,但它使发现问题变得更加困难,并且对以后可能需要进行更改的人来说也更加困难。

于 2008-09-17T19:12:33.907 回答
14

需要记住的一件事是,您通常可以使用 eval() 在其他受限环境中执行代码 - 阻止特定 JavaScript 函数的社交网站有时可能会通过在 eval 块中分解它们而被愚弄 -

eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');

因此,如果您希望在可能不允许的地方运行一些 JavaScript 代码(Myspace,我正在看着您...),那么 eval() 可能是一个有用的技巧。

但是,由于上述所有原因,您不应该将它用于您自己的代码,因为您可以完全控制它——这只是没有必要的,最好归类到“棘手的 JavaScript hacks”架子上。

于 2008-09-17T21:23:03.063 回答
13

除非您让 eval() 成为动态内容(通过 cgi 或输入),否则它与您页面中的所有其他 JavaScript 一样安全可靠。

于 2008-09-17T19:24:41.023 回答
8

与其他答案一起,我认为 eval 语句不能具有高级最小化。

于 2012-01-03T03:52:47.697 回答
6

这是一个可能的安全风险,它有不同的执行范围,而且效率很低,因为它为代码的执行创建了一个全新的脚本环境。有关更多信息,请参见此处:eval

不过,它非常有用,并且适度使用可以增加很多好的功能。

于 2008-09-17T19:37:46.187 回答
5

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

于 2008-09-17T19:13:53.233 回答
5

只要您知道您在什么环境中使用它,它不一定那么糟糕。

如果您的应用程序使用eval()XMLHttpRequest返回到您自己的站点的某个 JSON 创建一个对象,该对象是由您受信任的服务器端代码创建的,那么这可能不是问题。

不受信任的客户端 JavaScript 代码无论如何也做不了那么多。如果您正在执行的事情eval()来自合理的来源,那么您很好。

于 2008-09-17T20:10:08.700 回答
4

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

于 2008-09-17T20:06:41.813 回答
4

如果您希望用户输入一些逻辑函数并计算 AND OR,那么 JavaScript eval 函数是完美的。我可以接受两个字符串和eval(uate) string1 === string2等。

于 2010-03-09T14:28:37.937 回答
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()构造函数在大多数情况下与 using 类似eval(),因此应避免使用。

在幕后,JavaScript 仍然需要评估并执行您作为编程代码传递的字符串:

// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);

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

使用 new Function() 构造函数与 eval() 类似,应谨慎使用。它可能是一个强大的结构,但经常被滥用。如果你绝对必须使用eval(),你可以考虑使用 new Function() 代替。

有一个小的潜在好处,因为在 new Function() 中评估的代码将在本地函数范围内运行,因此在被评估的代码中使用 var 定义的任何变量都不会自动成为全局变量。

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

于 2017-05-08T14:06:35.490 回答
2

如果您正在执行用户提交的代码,除了可能存在的安全问题外,大多数时候还有一种更好的方法,它不需要在每次执行代码时都重新解析代码。匿名函数或对象属性可以取代 eval 的大多数用途,并且更加安全和快捷。

于 2008-09-17T19:14:49.020 回答
2

随着下一代浏览器带有某种 JavaScript 编译器的出现,这可能会成为一个更大的问题。对于这些较新的浏览器,通过 Eval 执行的代码的性能可能不如您的 JavaScript 的其余部分。有人应该做一些分析。

于 2008-09-17T19:20:41.830 回答
2

这是一篇关于 eval 以及它如何不是邪恶的好文章: http ://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/

我并不是说你应该用完并开始在任何地方使用 eval() 。事实上,运行 eval() 的好用例很少。代码清晰度、可调试性和性能肯定存在不容忽视的问题。但是当你遇到 eval() 有意义的情况时,你不应该害怕使用它。尝试先不要使用它,但不要让任何人吓到你认为当 eval() 使用得当时,你的代码更脆弱或更不安全。

于 2014-10-08T09:04:57.570 回答
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

于 2014-10-25T15:17:51.680 回答
2

JavaScript 引擎在编译阶段执行了许多性能优化。其中一些归结为能够在词法分析时对代码进行本质上的静态分析,并预先确定所有变量和函数声明的位置,以便在执行期间解析标识符所需的工作更少。

但是如果引擎在代码中发现了一个 eval(..),它本质上必须假设它对标识符位置的所有感知都可能是无效的,因为它无法在词法分析时准确地知道你可以传递给 eval(..) 的代码是什么。修改词法范围,或者您可以传递给的对象的内容,以创建要查询的新词法范围。

换句话说,在悲观的意义上,如果 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

于 2016-01-30T06:34:03.717 回答
2

这并不总是一个坏主意。以代码生成为例。我最近编写了一个名为Hyperbars的库,它弥补了virtual-domhandlebars之间的差距。它通过解析把手模板并将其转换为随后由 virtual-dom 使用的超脚本来实现这一点。超脚本首先生成为字符串,然后在返回之前将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()不是问题,因为您只需要解释一次生成的字符串,然后多次重复使用可执行输出。

如果您对此感到好奇,可以查看代码生成是如何实现

于 2016-11-28T11:04:26.870 回答
2

eval()我想说的是,如果您在浏览器中运行的 javascript 中使用它并不重要。*(警告)

所有现代浏览器都有一个开发人员控制台,您可以在其中执行任意 javascript,任何半智能开发人员都可以查看您的 JS 源代码,并将他们需要的任何内容放入开发控制台以执行他们想要的操作。

*只要您的服务器端点对用户提供的值进行了正确的验证和清理,那么在您的客户端 javascript 中解析和评估的内容就无关紧要了。

但是,如果您要问它是否适合eval()在 PHP 中使用,答案是否定的,除非您将可能传递给您的 eval 语句的任何值列入白名单。

于 2018-03-01T11:30:39.323 回答
2

垃圾收集

浏览器垃圾收集不知道是否可以从内存中删除经过评估的代码,因此它只是将其保存到页面重新加载。如果您的用户只是很快就在您的页面上,这还不错,但这对于 webapp 来说可能是一个问题。

这是一个演示问题的脚本

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

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

像上面的代码这样简单的事情会导致少量内存被存储,直到应用程序死掉。当 evaled 脚本是一个巨大的函数并在间隔调用时,情况会更糟。

于 2019-08-13T04:32:18.690 回答
1

我不会试图反驳之前所说的任何东西,但我会提供 eval() 的这种用法(据我所知)不能以任何其他方式完成。可能有其他方法可以对此进行编码,也可能有对其进行优化的方法,但这是直接完成的,为了清楚起见,没有任何花里胡哨,以说明 eval 的使用确实没有任何其他替代方案。即:动态(或更准确地说)以编程方式创建的对象名称(与值相反)。

//Place this in a common/global JS lib:
var NS = function(namespace){
    var namespaceParts = String(namespace).split(".");
    var namespaceToTest = "";
    for(var i = 0; i < namespaceParts.length; i++){
        if(i === 0){
            namespaceToTest = namespaceParts[i];
        }
        else{
            namespaceToTest = namespaceToTest + "." + namespaceParts[i];
        }

        if(eval('typeof ' + namespaceToTest) === "undefined"){
            eval(namespaceToTest + ' = {}');
        }
    }
    return eval(namespace);
}


//Then, use this in your class definition libs:
NS('Root.Namespace').Class = function(settings){
  //Class constructor code here
}
//some generic method:
Root.Namespace.Class.prototype.Method = function(args){
    //Code goes here
    //this.MyOtherMethod("foo"));  // => "foo"
    return true;
}


//Then, in your applications, use this to instantiate an instance of your class:
var anInstanceOfClass = new Root.Namespace.Class(settings);

编辑:顺便说一句,我不建议(出于迄今为止指出的所有安全原因)您将对象名称基于用户输入。不过,我无法想象你有什么好的理由要这样做。不过,我想我会指出这不是一个好主意:)

于 2015-02-12T18:45:04.167 回答