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

时间:2014-01-08 22:55:54

标签: javascript

请考虑以下代码:

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);
}

3 个答案:

答案 0 :(得分:8)

首先,让我们谈谈我们期望的内容。

我会天真地期望两个案例都返回undefined

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

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

  • 就像langue规范中的 for strict mode 一样。

更新:在通过规范挖掘更多内容后, Firefox在这里是正确的。

这是Firefox正在做的事情

可视化:

  
      
  1. f = eval("" + f); //将左侧设置为我们所在的函数f
  2.   
  3. f = eval(“”+ f); //在此功能范围内声明新功能f
  4.   
  5. f = undefined; //来自undefined === eval("function(){}"); *
  6.   

*因为函​​数声明不返回任何内容 - 就像函数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,并在作业的左侧评估右侧。

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

请注意,下一部分在输入评估代码中说明:

  

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

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


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

  • f“查找”f =(因为左侧必须首先评估 。这是指{em> local { {1}}。也就是说,首先评估左侧。
  • 执行返回f的{​​{1}}调用,但声明eval的新本地函数。
  • 由于来自undefined的{​​{1}}在函数本身之前被评估,当我们为其分配undefined时,我们实际上正在替换全局函数
  • 因此,当我们在f内部时,我们指的是在eval中声明的本地副本,因为它在范围链中更接近。
  • 当我们在外面做f时,我们现在指的是我们指定为未定义的'全球'f =

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

IE和Chrome错误地指定了本地console.log。由于规范明确告诉我们,这显然是不正确的:

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

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

  4.   

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

link to relevant esdiscuss thread

答案 1 :(得分:2)

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

  

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

两件事:

  • eval函数声明确实返回undefined。可以在评估时对其进行重新定义,然后再将undefined分配给f
  • f是函数f中的局部变量,因为命名函数在它们自己的作用域中可用。

http://jsfiddle.net/G2Q2g/5/

中检查此行为

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

  

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

答案 2 :(得分:0)

将您的代码更改为

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返回最后一个表达式的值。这就是你在这里看到的。