为什么EcmaScript 5严格模式要花费很大的精力限制标识符`eval`?

4

根据规范(附录C),严格模式的代码不能执行任何可能会赋值给名称为eval的标识符的操作。我可以理解限制实际eval函数的使用,但我不明白限制名称使用的目的是什么?

2个回答

10

bobince的观点基本正确。(我在SpiderMonkey上工作,这是Mozilla的JavaScript引擎,已经在其中实现了ES5的各个部分,并且随时间允许关注ECMAScript的讨论。)您(包括实现者和读者)确实希望eval是规范的eval,并且希望arguments是规范的arguments。使用严格模式,您可以大多数情况下达到这个目的。

但我要指出,ES5对此处的限制不如理想。首先,为了稍微纠正bobince的观点,即使在严格模式下,您也无法确定eval是否是原始的eval函数:

"use strict";
this.eval = function() { return "ohai"; };
eval("7 + 10"); // "ohai"
这是众所周知的(对于JS爱好者而言)全局对象错误:脚本使用一个全局对象来解析名称,该对象在多个脚本间共享,可命名和可修改。如果你不能引用绑定全局变量的对象,你就不会有这个问题。很可能ES6将通过另一种可选系统来解决这个问题(可能类似于MIME类型,但这还不确定),始终针对整个脚本。
但即使没有可命名、可变的全局对象,你仍然会遇到问题,因为严格模式可以被限定在函数作用域内。
function outer()
{
  var eval = function() { return "kthxbai"; };
  function inner()
  {
    "use strict";
    return eval("2 + 5");
  }
  return inner();
}
outer(); // "kthxbai"
即使使用严格模式,这些问题仍然存在,并且在ES6之前不会消失,因为它可能会删除全局对象并无条件地强制执行严格模式限制。因此,在严格模式下,eval仍然有点奇怪,因为它可以引用非eval的内容。但是处理起来并不困难——在运行时,实现可以检查真正的eval,如果失败,就像处理语法使用除eval之外的名称一样处理。这要求特别处理类似于eval(...)这样的表达式。但任何一个好的实现都会这样做,因为eval的非静态可观察行为(改变局部变量和参数,引入新变量[现在在严格模式下已去除——严格模式eval代码中的变量声明是局部的eval代码]等) ,所以这并不是真正的负担。值得注意的是,这些都不适用于arguments作为虚假特殊形式。如果通过函数作用域使用严格模式,则会看到函数的arguments而不是分配在外部作用域中的arguments;或者通过全局作用域使用它,则arguments没有特殊行为。(为什么在全局严格模式代码中禁止修改arguments?可能是简单性,加上强制开发人员在任何地方将它们视为更特殊的形式,但我不确定。)

你可能知道为什么在严格模式下仍然允许使用 eval/ arguments 作为 标签名称 吗? - Šime Vidas
我不确定,但是基于在严格模式代码中禁止使用eval/arguments作为变量名的原因,我还是猜了一个相当好的答案。 :-) - Jeff Walden

7
我只能推测,但我认为ES5-strict是在说evalarguments应该被视为原始语法,而不是标识符。这两个特性应该在语法级别上实现是合理的,因为它们具有惊人的奇妙魔法行为,这些行为无法通过普通函数来复制。
(特别是eval可能会写入调用它的函数中的局部变量,而将值写入arguments会奇怪地改变对应于参数的局部变量的值。尽管这种行为似乎正在严格模式下消失,谢天谢地。)
出于兼容性的原因,ES5实际上不能使evalarguments成为语法。所以他们做到了最接近的事情,也就是说标识符arguments总是指向arguments魔法,标识符eval则总是专门指向eval魔法。
如果JS引擎可以确定一个函数是否包含魔法,它还可以提高优化的可能性。

4
实际上,在我们致力于开发JScript.NET时,我们经常希望有一种方法可以绝对肯定地保证:(1)调用名为“eval”的东西确实是调用了eval,(2)调用非“eval”命名的东西不是在调用eval。ECMAScript的设计使得静态确定这一点极其困难,因此很难编写优化编译器。我已经超过十年没有参加技术委员会了,所以无法权威地谈论他们的动机,但如果我再次参加该委员会,我肯定会想要这样的限制。 - Eric Lippert
ES5 修改了 eval,使其成为“直接”或“间接”。 直接 eval 仅通过形式为 eval(...) 的表达式发生。 这种形式做的事情与以前一样 - 引入新绑定,变得邪恶等等。 间接 eval 永远不会 影响本地作用域 - 而是被强制在全局范围内执行。 通过这个改变,您可以确切地知道在任何不包括直接 eval 的函数中可能发生什么 - 如果有,您将进行大幅度的优化。 从技术上讲,ES3 允许实现禁止间接 eval,但很少有人这样做,也许 JScript.NET 不是其中之一。 - Jeff Walden
因此,继续(字符用完了),需要间接的eval来影响全局范围,以消除最有害的eval形式。 (直接的eval不太好,但至少可以检测到其不良行为,以便防范。)间接的eval仍然可以修改全局范围。 但是,函数中的任何内容都可能以几乎无法静态预测的方式执行此操作。 对全局名称的引用永远无法像本地名称一样进行优化,并且间接的eval不会改变这种情况。 只有ES6删除全局对象(如果发生)才能做到这一点。 - Jeff Walden
ж ҮиҜҶз¬Ұeval并дёҚжҖ»жҳҜжҢҮеҗ‘дёҖдёӘеңЁдёҘж јжЁЎејҸдёӢиҜ„дј°е…¶еҸӮж•°зҡ„еҮҪж•°гҖӮиҖғиҷ‘иҝҷдёӘеҢ…еҗ«дёҘж јжЁЎејҸеҮҪж•°зҡ„йқһдёҘж јзЁӢеәҸпјҡeval = alert; function foo(s) { "use strict"; eval(s); }гҖӮ - Mike Samuel

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