【问题标题】:Indirect eval call in strict mode严格模式下的间接 eval 调用
【发布时间】:2013-10-14 10:14:06
【问题描述】:

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

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

但是,如果我在全局范围内对 eval() 执行 间接 调用(应该是同一件事,对吗?),它的行为就好像它不是在严格模式下(如果你不信,看this 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())为@987654334 中定义的变量创建一个新范围@call,但是这里似乎并非如此。 (ECMA-262 规范第 5 版10.4.2

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

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

【问题讨论】:

  • 对我来说就像一个错误。你是如何执行上面的代码的?哪个浏览器/JS 解释器?
  • @AaronDigulla:在给定的 JSFiddle 中,Chrome 30。
  • @AaronDigulla:Firefox 24 和 IE10 也表现出这种行为。
  • 好问题。正在努力:) 不过有人可能会打败我。
  • Safari 6、webkit nightly 和 opera 也是如此。

标签: javascript eval ecmascript-5


【解决方案1】:

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 Prologue 开头,或者对 eval 的调用是直接调用,则它是严格的 eval 代码。

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


首先是一个好问题。

“Eval Code”比直接或间接调用eval更通用。

让我们检查一下确切的规格for the eval function

15.1.2.1 评估(x)

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

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

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

  3. 设 evalCtx 为 eval 代码程序建立新的执行上下文 (10.4.2) 的结果。

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

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

那么,让我们来看看 10.4.2 告诉我们什么,你引用了 - 具体来说,让我们看看第一个子句:

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

那么什么是直接调用?

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

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

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

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

eval('var a = 1;'); 中,确实评估它的结果有一个引用名称eval 并对其调用GetValue 解析返回内置函数。

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

什么是参考名称?

8.7 in the spec 部分告诉我们:

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

这需要我们查看GetReferencedName

GetReferencedName(V)。返回引用 V 的被引用名称组件。

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

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

【讨论】:

  • 好吧,还不错。但是,这并不能解释第 3 步 - 如果它处于严格模式,则为 eval 调用创建一个新范围。
  • @Qantas94Heavy 我不得不承认 EcmaScript 6 的“如果直接为真并且直接调用 eval 的代码是严格代码,那么让 strictCaller 为真。否则,让 strictCaller 为假。”清楚了很多。 ES5 让你走得更远。严格模式代码定义为:“如果 Eval 代码以包含 Use Strict 指令的指令序言开头,或者对 eval 的调用是直接调用,那么它就是严格的 eval 代码” - 调用实际上是直接调用 - 条件在第 3 步是假的。
  • 我的意思是调用不是直接调用。
  • 是的,你是(假设“use strict”在指令序言中——作为第一件事)。这正是规范的含义:)
  • 这是一个很好的问题,有一个惊人的答案。感谢您提供一些非常有见地的评论!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-24
  • 2014-07-24
  • 1970-01-01
  • 2016-08-15
相关资源
最近更新 更多