57

我了解eval()在非严格环境中的工作原理,但是eval()在严格模式下使用的情况完全让我感到困惑。当eval()在全局范围内直接调用时,变量被保存在新的eval()范围内:

'use strict';
eval('var a = 1;');
console.log(a); // ReferenceError: a is not defined

但是,如果我执行间接调用eval()(应该是同一件事,对吗?),它的行为就好像它不是在严格模式下(如果你不相信我,请参阅这个 JSFiddle):

'use strict';
(0, eval)('var a = 1;'); // indirect call to eval
console.log(a); // 1???

为了解释什么(0, eval):请参阅Why does google main page use (0, obj.func)(args) syntax?.

至少根据我对如何的理解eval()应该如何在严格模式下工作的理解,它意味着(无论eval()是直接调用还是间接调用)为eval()调用中定义的变量创建一个新范围,但这似乎不是案例在这里。(ECMA-262 规范第 5 版10.4.2

所有主流浏览器(包括 Internet Explorer 10、Chrome 30 和 Firefox 24)都是这种情况,所以我认为这不是错误。他们不是都打算做同样的事情吗?如果不是,为什么会这样?

注意:是的,我知道使用的“危险” eval()——我只是想了解这背后的逻辑:)

4

1 回答 1

39

tl;博士

第二种(0, eval)('var a = 1;');情况其实不是直接调用。

您可以在以下位置更普遍地看到这一点:

(function(){ "use strict"
    var x = eval;
    x("var y = 10"); // look at me all indirect
    window.y;// 10
    eval("var y = 11");
    window.y;// still 10, direct call in strict mode gets a new context
})();

问题可见于:

如果 eval 代码是严格代码,那么(我:修复上下文)

但严格的 eval 代码定义为:

如果 Eval 代码以包含 Use Strict Directive 的 Directive Prologue 开头,或者对 eval 的调用是直接调用,则它是严格的 eval 代码。

由于调用不是直接的,因此 eval 代码不是严格的 eval 代码 - 并且执行是在全局范围内。


首先是好问题。

“评估代码”比直接或间接调用eval.

让我们检查一下函数的确切规格eval

15.1.2.1 评估 (x)

当使用一个参数 x 调用 eval 函数时,将执行以下步骤:

  1. 如果 Type(x) 不是字符串,则返回 x。

  2. 令 prog 为将 x 解析为 Program 的结果的 ECMAScript 代码。如果解析失败,抛出一个 SyntaxError 异常(另见第 16 条)。

  3. 让 evalCtx 成为为 eval 代码 prog建立新的执行上下文(10.4.2) 的结果。

  4. 令 result 为评估程序 prog 的结果。

  5. 退出正在运行的执行上下文 evalCtx,恢复之前的执行上下文。...

所以,让我们探讨一下 10.4.2 告诉我们的内容,您引用了这一点 - 具体来说,让我们看看第一个子句:

如果没有调用上下文或者 eval 代码没有通过对 eval 函数的直接调用 (15.1.2.1.1) 进行评估,则...初始化执行上下文,就好像它是全局执行上下文一样

那么什么是直接调用呢?

对 eval 函数的直接调用是表示为满足以下两个条件的 CallExpression 的调用:

作为在 CallExpression 中评估 MemberExpression 的结果的 Reference 有一个环境记录作为它的基值,它的引用名称是“eval”。

以该 Reference 作为参数调用抽象操作 GetValue 的结果是 15.1.2.1 中定义的标准内置函数。

那么,这MemberExpression两种情况是什么?

实际上eval('var a = 1;');,评估它的结果有一个参考名称eval,并且在其上调用GetValue解析返回内置函数。

(0, eval)('var a = 1;');评估成员表达式的结果中没有引用名称eval。(它确实解决了 GetValue 上的内置函数)。

什么是参考名称?

规范中的第8.7节告诉我们:

引用是解析的名称绑定。引用由三个部分组成,基值、被引用的名称和布尔值的严格引用标志。基值是未定义的、对象、布尔值、字符串、数字或环境记录 (10.2.1)。未定义的基值表示无法将引用解析为绑定。引用的名称是一个字符串。

这需要我们研究GetReferencedName

获取引用名称(V)。返回引用 V 的引用名称组件。

因此,虽然表达式(0,eval) === eval为真,但在评估函数时,由于命名,这实际上是间接调用。

我可以提供Function构造函数吗:)?

于 2013-10-14T11:19:19.567 回答