什么时候JavaScript的eval()不是邪恶的?
我正在编写一些JavaScript代码来parsing用户input的函数(对于类似电子表格的function)。 parsing了公式之后,我可以将它转换成JavaScript并在其上运行eval()
以产生结果。
但是,如果我可以避免使用eval()
,因为它是邪恶的(而且,无论正确还是错误,我一直认为它在JavaScript中更加邪恶,因为要评估的代码可能会改变由用户)。
那么,什么时候可以使用它?
我想花一点时间来解决你的问题的前提–eval()是“ 邪恶的 ”。 程序devise语言使用的“ 邪恶 ”一词通常意味着“危险的”,或者更确切地说,“能够以简单的命令造成很多伤害”。 那么,什么时候可以使用危险的东西呢? 当你知道危险是什么时,以及当你采取适当的预防措施。
我们来看看使用eval()的危险。 像其他所有事情一样,可能存在许多小的隐患,但是两大风险–eval()被认为是邪恶的原因是性能和代码注入。
- 性能 – eval()运行解释器/编译器。 如果你的代码被编译,那么这是一个很大的打击,因为你需要在运行时调用一个可能很重的编译器。 然而,JavaScript仍然是一种解释性语言,这意味着在一般情况下调用eval()并不是一个很大的性能影响(但请参阅下面的具体评论)。
- 代码注入 – eval()可能会在提升的权限下运行一串代码。 例如,以pipe理员身份运行的程序/ root永远不会想要eval()用户input,因为该input可能是“rm -rf / etc / important-file”或者更糟。 同样,浏览器中的JavaScript也没有这个问题,因为程序无论如何都是在用户自己的帐户中运行的。 服务器端JavaScript可能会有这个问题。
关于你的具体情况。 根据我的理解,你自己生成了string,所以假设你小心不要让像“rm -rf something-important”这样的string被生成,那么就没有代码注入的风险(但是请记住,这是非常非常在一般情况下很难保证这一点)。 另外,如果你在浏览器中运行,我相信代码注入是一个相当小的风险。
至于性能,你必须在编码容易性方面加以减轻。 这是我的意见,如果你parsing的公式,你也可以计算结果在parsing,而不是运行另一个parsing器(eval()内的一个)。 但是使用eval()编码可能更容易,性能命中可能不明显。 它看起来像eval()在这种情况下是没有比任何其他可能可以节省您一些时间的function更邪恶。
eval()
不是邪恶的。 或者,如果是这样的话,它就像反思,文件/networkingI / O,线程和IPC在其他语言中是“邪恶的”一样是邪恶的。
如果出于您的目的 , eval()
比手动解释更快,或者使您的代码更简单或更清晰,那么您应该使用它。 如果没有,那么你不应该。 就那么简单。
当你信任的来源。
在JSON的情况下,或多或less很难篡改源,因为它来自您控制的Web服务器。 只要JSON本身不包含用户上传的数据,使用eval就没有什么大的缺点。
在所有其他情况下,我会竭尽全力确保用户提供的数据符合我的规则,然后将其提供给eval()。
让我们得到真正的人:
-
现在,每个主stream的浏览器都有一个内置的控制台,你可能会被黑客利用,可以用任何值来调用任何函数 – 为什么他们会打扰使用一个评估语句 – 即使他们可以?
-
如果需要0.2秒来编译2000行JavaScript,那么如果我评估四行JSON,性能会下降吗?
即使克罗克福德对“eval是邪恶的”的解释也很薄弱。
"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中使用它(通过函数构造函数)。
我倾向于遵循克罗克福德的 eval()
的build议 ,并完全避免它。 即使似乎需要它的方式也不行。 例如, setTimeout()
允许你传递一个函数而不是eval。
setTimeout(function() { alert('hi'); }, 1000);
即使它是一个值得信赖的源代码,我也不会使用它,因为JSON返回的代码可能会出现乱码,最多只能做一些不可思议的事情,最糟糕的是会暴露一些不好的东西。
Eval是用于模板化代码的编译的补充。 通过模板,我的意思是你写一个简化的模板生成器,生成有用的模板代码,从而提高开发速度。
我写了一个框架,开发人员不使用EVAL,但他们使用我们的框架,反过来框架必须使用EVAL生成模板。
使用以下方法可以提高EVAL的性能; 而不是执行脚本,你必须返回一个函数。
var a = eval("3 + 5");
它应该组织为
var f = eval("(function(a,b) { return a + b; })"); var a = f(3,5);
cachingf肯定会提高速度。
另外Chrome允许非常容易地debugging这些function。
关于安全性,使用评估与否不会有什么区别,
- 首先,浏览器调用沙箱中的整个脚本。
- 在EVAL中任何邪恶的代码在浏览器本身都是邪恶的。 攻击者或任何人都可以轻松地在DOM中注入一个脚本节点,如果他/她能够评估任何东西,就可以做任何事情。 不使用EVAL不会有任何区别。
- 大多数服务器端的安全性是有害的。 糟糕的cookievalidation或者在服务器上执行不良的ACL会导致大多数攻击。
- 最近的Java漏洞等在Java的本地代码中出现。 JavaScript被devise为在沙箱中运行,而小程序则被devise为在带有证书的沙箱外运行,从而导致漏洞和许多其他事情。
- 编写模仿浏览器的代码并不困难。 您只需使用您最喜欢的用户代理string向服务器发送HTTP请求即可。 无论如何,所有testing工具都可以模拟浏览器。 如果攻击者想伤害你,EVAL是他们的最后一招。 他们有很多其他的方式来处理你的服务器端的安全。
- 浏览器DOM不能访问文件,也不能访问用户名。 实际上eval可以访问的机器上没有任何东西。
如果您的服务器端安全性足够稳定,任何人都可以从任何地方进行攻击,则不必担心EVAL。 正如我所提到的,如果EVAL不存在,攻击者有许多工具可以侵入你的服务器,而不pipe你的浏览器的EVAL能力如何。
Eval仅适用于生成一些模板,以便根据事先未使用的内容进行复杂的string处理。 比如,我会更喜欢
"FirstName + ' ' + LastName"
而不是
"LastName + ' ' + FirstName"
作为我的显示名称,它可以来自数据库,而不是硬编码。
当你需要使用eval()时,唯一的例子是当你需要运行dynamicJS时。 我在谈论你从服务器asynchronous下载的JS …
… 10次中的9次你可以很容易地避免重构。
我看到人们主张不使用eval,因为是邪恶的 ,但是我看到同样的人dynamic地使用Function和setTimeout,所以他们在引擎盖下使用eval:D
顺便说一句,如果你的沙箱不够确定(例如,如果你在一个允许代码注入的网站上工作),eval是你的最后一个问题。 安全的基本规则是所有的input都是邪恶的,但是在JavaScript的情况下, 即使 JavaScript本身也可能是邪恶的,因为在JavaScript中你可以覆盖任何函数,而你不能确定你是否使用真正的函数,如果一个恶意代码在你之前开始,你不能相信任何JavaScript内置函数:D
现在这个post的结尾是:
如果你真的需要它(80%的时间不需要eval),你确定你在做什么,只需使用eval(或更好的函数;)),闭包和OOP覆盖80/90%的在这种情况下,可以使用另一种逻辑来替代eval,剩下的就是dynamic生成的代码(例如,如果你正在编写一个解释器),正如你所说的评估JSON(在这里你可以使用Crockford安全评估)
微软解释了为什么IE浏览器中的浏览器中的eval()速度较慢, IE + JavaScript性能build议第2部分:JavaScript代码效率低下 。
如果您完全控制传递给eval
函数的代码,则可以使用它。
在Chrome(v28.0.1500.72)中进行debugging时,我发现如果variables没有用在产生闭包的嵌套函数中,那么variables不会被绑定到闭包。 我想,这是对JavaScript引擎的优化。
但是 :当在引起闭包的函数内部使用eval()
时,即使根本不使用外层函数的所有variables,也会将其绑定到闭包。 如果有人有时间testing是否可以产生内存泄漏,请在下面给我留言。
这是我的testing代码:
(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()
函数。 这一切都取决于function的名称 。 所以当用一个别名(比如说var noval = eval;
然后在一个内部函数noval(expression);
)中调用本地eval()
时,那么noval(expression);
的求值可能会失败,当它引用应该是闭包的一部分的variables时,但实际上不是。
eval
很less是正确的select。 虽然可能有很多情况下,您可以通过将脚本连接在一起并实时运行来完成所需的工作,但通常您可以使用更强大和可维护的技术:关联数组表示法( obj["prop"]
与obj.prop
相同),闭包,面向对象的技巧,function性技巧 – 用它们代替。
我的看法是,eval对于客户端的web应用程序来说是一个非常强大的function,并且安全…和JavaScript一样安全,不是。 :-)安全问题基本上是一个服务器端问题,因为现在,像Firebug这样的工具,你可以攻击任何JavaScript应用程序。
就客户端脚本而言,我认为安全问题是一个有争议的问题。 所有加载到浏览器中的东西都会受到操纵,应该这样处理。 当有更简单的方法来执行JavaScript代码和/或操作DOM中的对象(如浏览器中的URL栏)时,使用eval()语句的风险是零。
javascript:alert("hello");
如果有人想操纵他们的DOM,我说摇摆。 安全防止任何types的攻击应始终是服务器应用程序的责任期。
从实用的angular度来看,在可以做其他事情的情况下使用eval()是没有好处的。 但是,在某些情况下应该使用eval。 如果是这样的话,这绝对是可以做到的,没有任何冒险的风险。
<html> <body> <textarea id="output"></textarea><br/> <input type="text" id="input" /> <button id="button" onclick="execute()">eval</button> <script type="text/javascript"> var execute = function(){ var inputEl = document.getElementById('input'); var toEval = inputEl.value; var outputEl = document.getElementById('output'); var output = ""; try { output = eval(toEval); } catch(err){ for(var key in err){ output += key + ": " + err[key] + "\r\n"; } } outputEl.value = output; } </script> <body> </html>
什么时候JavaScript的eval()不是邪恶的?
我总是试图阻止使用eval 。 几乎总是有一个更清洁和可维护的解决scheme。 即使对于JSONparsing,也不需要 Eval。 Eval 增加了维护地狱 。 道格拉斯·克罗克福德(Douglas Crockford)这样的大师们并不是没有理由的。
但是我发现了一个应该使用它的例子:
当你需要通过expression。
例如,我有一个函数为我构build了一个通用的google.maps.ImageMapType
对象,但是我需要告诉它这个配方,它应该如何从zoom
和coord
参数中构造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
: import的例子。
通常如何做
var components = require('components'); var Button = components.Button; var ComboBox = components.ComboBox; var CheckBox = components.CheckBox; ... // That quickly gets very boring
但是在eval
和一个小帮手函数的帮助下,它变得更好看了:
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; }
只有在testing时,如果可能的话。 另外请注意,eval()比其他专门的JSON等评估者慢得多。
代码生成。 我最近写了一个名为Hyperbars的图书馆,它填补了virtual-dom和handlebars之间的空白。 它通过parsing一个句柄模板并将其转换为hyperscript来完成 。 hyperscript是作为一个string生成的,在返回之前, 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()
的性能也不是问题,因为你只需要解释生成的string一次,然后重复使用可执行的输出。
如果你对这里有好奇,你可以看看代码生成是如何实现的。
没有理由不使用eval(),只要你能确定代码的来源来自你或实际的用户。 即使他可以操纵发送到eval()函数中的内容,但这不是一个安全问题,因为他可以操纵网站的源代码,因此可以自行更改JavaScript代码。
那么…什么时候不使用eval()? 只有当第三方有可能改变它时,Eval()才应该被使用。 就像拦截客户端和服务器之间的连接一样(但如果这是一个问题,请使用HTTPS)。 你不应该eval()来parsing其他人在论坛中写的代码。
如果真的需要eval不是邪恶的。 但是我偶然发现的eval的用法中有99.9%是不需要的(不包括setTimeout的东西)。
对我来说,邪恶不是一个performance甚至是一个安全问题(好吧,间接就是两者)。 eval所有这些不必要的用途都会join维护地狱。 重构工具被抛弃。 search代码很难。 这些事件的意想不到的影响是军团。
当你没有macros时,Eval对代码生成很有用。
对于一个愚蠢的例子,如果你正在编写一个Brainfuck编译器,你可能需要构造一个函数来执行指令序列作为一个string,并对其进行评估以返回一个函数。
当使用parsing函数(例如,jQuery.parseJSON)parsingJSON结构时,它需要JSON文件的完美结构(每个属性名称都使用双引号)。 但是,JavaScript更加灵活。 因此,你可以使用eval()来避免它。