问题 为什么重新定义自己的功能在Chrome / IE和Firefox中表现不同?


请考虑以下代码:

function f() {
    f = eval("" + f);
    console.log("Inside a call to f(), f is: \n%s", f);
}

f();

console.log("After a call to f(), f is: \n%s", f);

我期望 f 在执行期间始终定义。但是,在Chrome和IE中,它是 undefined 当第一个 console.log 被调用,在Firefox中,它是 undefined 当第二个 console.log 被调用。

为什么是 f 不总是定义?为什么Chrome / IE和Firefox的行为有所不同?

http://jsfiddle.net/G2Q2g/

Firefox 26上的输出:

在调用f()时,f是:

function f() {
    f = eval("" + f);
    console.log("Inside a call to f(), f is: \n%s", f);
}

在调用f()之后,f是:

undefined

Chrome 31和IE 11上的输出:

在调用f()时,f是:

undefined

在调用f()之后,f是:

function f() {
    f = eval("" + f);
    console.log("Inside a call to f(), f is: \n%s", f);
}

3158
2018-01-08 22:55


起源

建议更好的标题欢迎:) - Dagg Nabbit
也许他们的游戏机不同......? - Cilan
@adeneo这肯定与这个有关 eval 因为当我们不执行时 "" 在评估中 - 它的工作原理。另外,当我们使用时 new Function 或者将它分配给自己 - 它有效。 - Benjamin Gruenbaum
如果你做除了评估之外的其他事情,它会按预期工作 - > jsfiddle.net/G2Q2g/2 - adeneo
我现在问了es-discuss esdiscuss.org/topic/behavior-of-eval-in-non-strict-mode - Benjamin Gruenbaum


答案:


首先,让我们谈谈我们所期望的。

我会 天真 期待两个案件都返回 undefined

  • 就像: eval("function foo(){}") 返回undefined。

  • 就像我们有一个函数声明一样 - 它不会返回函数值但会设置它。

  • 就像langue规范所说的那样 对于严格模式

更新:通过规范挖掘更多 - Firefox在这里是正确的。

这是Firefox正在做的事情

可视化:

  1. F  = eval("" + f); //将左侧设为功能 f 我们在
  2. f =  eval(“”+ f); //声明一个新函数 f 在这个功能的范围内
  3. f = undefined;       //从那以后 undefined === eval("function(){}"); *

*因为函数声明不返回任何东西 - 就像函数foo(){}没有 返回值

由于f是在步骤1中决定的,所以现在对我们所处的函数的引用被undefined覆盖并声明了一个局部闭包 f 用相同的代码声明。 现在我们做的时候:

console.log("Inside a call to f(), f is: \n%s",F) // f是局部闭包变量,它最接近

突然间,很明显我们得到了这个函数 - 它是一个成员变量。

但是,一旦我们逃脱了这个功能

console.log("After a call to f(), f is: \n%s",F);

这里,f是未定义的,因为我们在步骤1中覆盖了它。

Chrome和IE错误地将其分配给错误 f 并在作业左侧评估右侧。

为什么它在严格模式下工作

请注意,输入eval代码中的下一部分说明:

让strictVarEnv成为调用NewDeclarativeEnvironment传递LexicalEnvironment作为参数的结果。

这解释了为什么它在严格模式下工作 - 它都在新环境中运行。


同样的事情,但更多的文字和更少的图形

  • “找” f 从 f = (因为必须评估左侧 第一。这指的是 本地 备份 f。也就是说,首先评估左侧。
  • 执行 eval 回电的电话 undefined 但声明了一个新的本地函数 f
  • 以来 f 从 f = 在函数本身之前进行了评估,当我们为它分配undefined时,我们实际上正在替换全局函数
  • 所以,当我们这样做 console.log 我们指的是在eval中声明的本地副本,因为它在范围链中更接近。
  • 当我们在外面做的时候 console.log,我们现在指的是'全球' f 我们分配了未定义的。

诀窍是,我们分配的f和我们记录的f是两个不同的fs。这是因为总是首先评估赋值的左侧(规范中的第11.13.1节)。

IE和Chrome犯了分配给本地的错误 f。由于规范明确告诉我们,这显然是不正确的:

  1. 让lref成为评估LeftHandSideExpression的结果。

  2. 让rref成为评估AssignmentExpression的结果。

因此,正如我们所看到的那样,首先需要评估lref。

链接到相关的esdiscuss线程


8
2018-01-08 23:20



如果定义了chrome f 在全球范围内,为什么没有 window.f? - cookie monster
@cookiemonster我得到了 window.f 在我的浏览器窗口(数字,否则第二个 .log 不会工作)。顺便一提 hg.mozilla.org/mozilla-central/file/b9ca7a6f891a/js/src/builtin/... 如果你想调查(这是我正在寻找答案的地方) - Benjamin Gruenbaum
奇怪。我明白了 undefined 在Linux上的Chromium中。 jsfiddle.net/G2Q2g/9 - cookie monster
@cookiemonster那是因为你引入了另一个范围 - JSFiddle包含了一个调用 onloadhandler so eval将函数放入 那 范围 。这里 - jsfiddle.net/zHvTB 。 - Benjamin Gruenbaum
是的,但这与问题中的jsFiddle相同。我假设你的答案是基于这个例子。 - cookie monster


对不起,我现在只回答你的第一个问题: - /

我希望在执行期间始终定义f。为什么f不总是被定义?

两件事情:

  • eval函数声明确实返回 undefined。它可以在评估时重新定义,但您可以分配 undefined 至 f 其后。
  • f 是函数中的局部变量 f,因为命名函数可以在自己的范围内使用。

检查此行为 http://jsfiddle.net/G2Q2g/5/

所以现在你至少可以问一下

为什么 undefined 当第二个 console.log 在Firefox中调用,而不是Opera,Chrome和IE中的正确行为?


2
2018-01-08 23:18



啊哈,你是对的,没有想到这一点。 jsfiddle.net/G2Q2g/7 - Dagg Nabbit
我仍然不确定firefox出了什么问题。它是真实的函数语句,它确实打开了本地的范围 f? - Bergi
我也不确定,还是试着绕过BG的答案。 - Dagg Nabbit


将您的代码更改为

function f() {
    f = eval("(" + f + ")");
    console.log("Inside a call to f(), f is: \n%s", f);
};

f();

console.log("After a call to f(), f is: \n%s", f);

它会起作用。请注意,函数源被包装到“(”“)”括号中,使其成为左值表达式而不是声明。

这里是 http://jsfiddle.net/G2Q2g/8/

更新:

eval(str) 解析并执行字符串作为程序(ECMAScript术语)。 函数声明本身就是一个声明,它没有任何价值。但是这个 ( func() ... )  是一个有价值的表达。并且eval返回最后一个表达式的值。那就是你在这里看到的。


0
2018-01-08 23:31



我认为问题不在于让它发挥作用。我认为问题在于如何理解其行为。 - cookie monster
@cookiemonster啊,谢谢。我已经更新了。 - c-smile
这不是我在评论Bergi答案时留下的小提琴吗? - Dagg Nabbit
@DaggNabbit是的,它是一样的。还没有看到它。 - c-smile