关于编码风格:JavaScript的eval()何时不是邪恶的?

When is JavaScript's eval() not evil?

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

但是,我总是回避使用eval()如果我可以避免它,因为它是邪恶的(并且正确或错误地,我一直认为它在JavaScript中更加邪恶,因为要评估的代码可能会被更改 由用户)。

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


我想花一点时间来解决你的问题的前提 - eval()是"邪恶的"。编程语言人使用的"邪恶"一词通常意味着"危险",或者更准确地说"能够通过简单的命令造成大量伤害"。那么,什么时候可以使用危险的东西呢?当您知道危险是什么时,以及何时采取适当的预防措施。

至关重点,我们来看看使用eval()的危险性。可能存在许多小的隐患,就像其他一切一样,但两个大的风险 - eval()被认为是邪恶的原因 - 是性能和代码注入。

  • 性能 - eval()运行解释器/编译器。如果你的代码是编译的,那么这是一个很大的问题,因为你需要在运行时调用一个可能很重的编译器。但是,JavaScript仍然主要是一种解释语言,这意味着在一般情况下调用eval()并不是一个很大的性能影响(但请参阅下面的具体说明)。
  • 代码注入 - eval()可能在提升的权限下运行一串代码。例如,以管理员/ root身份运行的程序永远不会想要eval()用户输入,因为该输入可能是"rm -rf / etc / important-file"或更糟。同样,浏览器中的JavaScript没有这个问题,因为程序无论如何都在用户自己的帐户中运行。服务器端JavaScript可能存在这个问题。

根据您的具体情况而定。根据我的理解,你自己生成字符串,所以假设你小心不要生成像"rm -rf something-important"这样的字符串,那么就没有代码注入的风险(但请记住,它非常非常在一般情况下很难确保这一点)。此外,如果你在浏览器中运行,那么代码注入是一个非常小的风险,我相信。

至于性能,你将不得不重视编码的简易性。我认为,如果你正在解析公式,你也可以在解析期间计算结果,而不是运行另一个解析器(eval()中的一个)。但是使用eval()进行编码可能更容易,并且性能损失可能不明显。看起来eval()在这种情况下并不比任何其他可以节省你一些时间的函数更邪恶。


eval()不是邪恶的。或者,如果是这样,那就像反射,文件/网络I / O,线程和IPC在其他语言中是"邪恶的"一样是邪恶的。

如果为了您的目的,eval()比手动解释更快,或者使您的代码更简单或更清晰......那么您应该使用它。如果不是,那么你不应该。就那么简单。


当你信任来源时。

在JSON的情况下,它或多或少难以篡改源,因为它来自您控制的Web服务器。只要JSON本身不包含用户上传的数据,使用eval就没有重大缺陷。

在所有其他情况下,我会竭尽全力确保用户提供的数据符合我的规则,然后再将其提供给eval()。


让我们真正的人:

  • 现在每个主要的浏览器都有一个内置的控制台,你可能会被黑客大量使用来调用任何有价值的函数 - 为什么他们会费心去使用eval语句 - 即使它们可以?

  • 如果编译2000行JavaScript需要0.2秒,如果我评估四行JSON,我的性能会下降吗?

  • 即使是克罗克福德对"eval is evil"的解释也很薄弱。

    eval is Evil, The eval function is the most misused feature of
    JavaScript. Avoid it

    正如克罗克福德本人可能会说的那样"这种说法往往会产生非理性的神经症。不要买它。"

    了解eval并了解它何时可能有用更为重要。例如,eval是评估软件生成的服务器响应的合理工具。

    BTW:Prototype.js直接调用eval五次(包括evalJSON()和evalResponse())。 jQuery在parseJSON中使用它(通过Function构造函数)。


    我倾向于遵循Crockford对eval()的建议,并完全避免它。即使看起来需要它的方式也没有。例如,setTimeout()允许您传递函数而不是eval。

    1
    2
    3
    setTimeout(function() {
      alert('hi');
    }, 1000);

    即使它是一个受信任的来源,我也不会使用它,因为JSON返回的代码可能会出现乱码,最多可能会做一些不好的事情,最坏的情况是暴露出坏事。


    在Chrome(v28.0.1500.72)中进行调试时,我发现如果变量未在生成闭包的嵌套函数中使用,则它们不会绑定到闭包。我猜,这是JavaScript引擎的优化。

    但是:当在导致闭包的函数内部使用eval()时,外部函数的所有变量都绑定到闭包,即使它们根本不被使用。如果有人有时间测试是否可以产生内存泄漏,请在下面给我留言。

    这是我的测试代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    (function () {
        var eval = function (arg) {
        };

        function evalTest() {
            var used ="used";
            var unused ="not used";

            (function () {
                used.toString();   // Variable"unused" is visible in debugger
                eval("1");
            })();
        }

        evalTest();
    })();

    (function () {
        var eval = function (arg) {
        };

        function evalTest() {
            var used ="used";
            var unused ="not used";

            (function () {
                used.toString();   // Variable"unused" is NOT visible in debugger
                var noval = eval;
                noval("1");
            })();
        }

        evalTest();
    })();

    (function () {
        var noval = function (arg) {
        };

        function evalTest() {
            var used ="used";
            var unused ="not used";

            (function () {
                used.toString();    // Variable"unused" is NOT visible in debugger
                noval("1");
            })();
        }

        evalTest();
    })();

    我想在此指出的是,eval()不一定是指本机eval()函数。这一切都取决于功能的名称。因此,当使用别名调用本机eval()(比如var noval = eval;然后在内部函数noval(expression);中)时,当expression引用应该属于闭包的变量时,expression的求值可能会失败,但是其实不是。


    Eval是编译的补充,用于模板化代码。通过模板我的意思是你编写一个简化的模板生成器,生成有用的模板代码,提高开发速度。

    我编写了一个框架,开发人员不使用EVAL,但他们使用我们的框架,反过来,框架必须使用EVAL生成模板。

    使用以下方法可以提高EVAL的性能;而不是执行脚本,您必须返回一个函数。

    1
    var a = eval("3 + 5");

    它应该被组织为

    1
    2
    3
    var f = eval("(function(a,b) { return a + b; })");

    var a = f(3,5);

    缓存f肯定会提高速度。

    此外,Chrome还可以轻松调试此类功能。

    关于安全性,使用eval与否将几乎没有任何区别,

  • 首先,浏览器在沙箱中调用整个脚本。
  • 任何在EVAL中都是邪恶的代码,在浏览器本身都是邪恶的。攻击者或任何人都可以轻松地在DOM中注入脚本节点,并且如果他/她可以评估任何内容,则可以执行任何操作。不使用EVAL不会有任何区别。
  • 主要是糟糕的服务器端安全性是有害的。服务器上的Cookie验证不佳或ACL实施不当会导致大多数攻击。
  • Java的本机代码中存在最近的Java漏洞等。 JavaScript曾经并且被设计为在沙箱中运行,而applet被设计为在具有证书等的沙箱之外运行,从而导致漏洞和许多其他事情。
  • 编写用于模仿浏览器的代码并不困难。您所要做的就是使用您最喜欢的用户代理字符串向服务器发出HTTP请求。无论如何,所有测试工具都会模拟浏如果攻击者想要伤害你,EVAL是他们的最后手段。他们还有许多其他方法来处理您的服务器端安全性。
  • 浏览器DOM无权访问文件而不能访问用户名。事实上,eval可以访问的机器上没有任何东西。
  • 如果您的服务器端安全性足够强大,任何人都可以从任何地方进行攻击,那么您不必担心EVAL。正如我所提到的,如果EVAL不存在,攻击者可以使用许多工具来攻击您的服务器,而不管您的浏览器的EVAL功能如何。

    Eval仅适用于生成一些模板,以根据事先未使用的内容进行复杂的字符串处理。例如,我更喜欢

    1
    "FirstName + ' ' + LastName"

    相反

    1
    "LastName + ' ' + FirstName"

    作为我的显示名称,它可以来自数据库而且不是硬编码的。


    我看到人们主张不使用eval,因为它是邪恶的,但是我看到同样的人动态地使用Function和setTimeout,所以他们在引擎盖下使用eval:D

    顺便说一句,如果您的沙箱不够确定(例如,如果您正在使用允许代码注入的站点上工作),则eval是您的最后一个问题。安全性的基本规则是所有输入都是邪恶的,但是在JavaScript的情况下,即使JavaScript本身也可能是邪恶的,因为在JavaScript中你可以覆盖任何函数而你无法确定你是否正在使用真正的函数,所以,如果恶意代码在您之前启动,则您无法信任任何JavaScript内置函数:D

    现在这篇文章的后记是:

    如果你真的需要它(80%的时间不需要eval)并且你确定你正在做什么,只需使用eval(或更好的功能;)),闭合和OOP覆盖80/90%的可以使用其他类型的逻辑替换eval的情况,其余的是动态生成的代码(例如,如果您正在编写解释器)并且您已经说过评估JSON(这里您可以使用Crockford安全评估;))


    微软解释了为什么eval()在IE博客上的浏览器速度慢,IE + JavaScript性能建议第2部分:JavaScript代码效率低下。


    您应该使用eval()的唯一实例是您需要动态运行动态JS。我说的是你从服务器上异步下载的JS ......

    ... 10次中的9次你可以通过重构轻松避免这样做。


    如果您完全控制传递给eval函数的代码,则可以使用它。