一个非常好的问题。:-)
区别:
这和你的detachEvent
情况不同的是,在这里,你不在乎“函数”内部和外部的函数引用是一样的,只是代码是一样的。在这种detachEvent
情况下,重要的是您在“函数”内部和外部看到相同的函数引用,因为这就是detachEvent
工作方式,通过分离您提供的特定函数。
两个功能?
是的。CMS 指出,当 IE (JScript) 看到一个命名函数表达式时,它会创建两个函数,就像代码中的函数表达式一样。(我们会回到这一点。)有趣的是,你同时给他们两个都打了电话。对真的。:-) 初始调用调用表达式返回的函数,然后所有使用该名称的调用调用另一个。
稍微修改你的代码可以使它更清楚一点:
var testaroo = 0;
var f = function executeOnLoad() {
if (testaroo++ < 5) {
setTimeout(executeOnLoad, 25);
return;
}
alert(testaroo); // alerts "6"
};
f();
最后f();
的 调用函数表达式返回的函数,但有趣的是,该函数只调用一次。所有其他时间,当它通过executeOnLoad
引用调用时,它是另一个被调用的函数。但是由于这两个函数都关闭相同的数据(包括testaroo
变量)并且它们具有相同的代码,所以效果非常像只有一个函数。不过,我们可以证明有两种方式与 CMS 所做的非常相似:
var testaroo = 0;
var f = function executeOnLoad() {
if (testaroo++ < 5) {
setTimeout(executeOnLoad, 0);
return;
}
alert(testaroo); // alerts "6"
// Alerts "Same function? false"
alert("Same function? " + (f === executeOnLoad));
};
f();
这也不仅仅是名称的假象,JScript确实创建了两个函数。
这是怎么回事?
基本上,实现 JScript 的人显然决定将命名函数表达式作为函数声明和函数表达式来处理,在过程中创建两个函数对象(一个来自“声明”,一个来自“表达式”)并且几乎可以肯定这样做是在不同的时间。这是完全错误的,但他们就是这样做的。令人惊讶的是,即使是 IE8 中的新 JScript 也延续了这种行为。
这就是您的代码看到(并使用)两个不同功能的原因。这也是CMS提到“泄漏”这个名字的原因。转移到他的示例的稍微修改的副本:
function outer() {
var myFunc = function inner() {};
alert(typeof inner); // "undefined" on most browsers, "function" on IE
if (typeof inner !== "undefined") { // avoid TypeError on other browsers
// IE actually creates two function objects: Two proofs:
alert(inner === myFunc); // false!
inner.foo = "foo";
alert(inner.foo); // "foo"
alert(myFunc.foo); // undefined
}
}
正如他所提到的,inner
是在 IE (JScript) 上定义的,而不是在其他浏览器上定义的。为什么不?对于不经意的观察者来说,除了这两个函数之外,JScript 关于函数名的行为似乎是正确的。毕竟,只有函数才能在 Javascript 中引入新的作用域,对吧?并且inner
功能在outer
. 但规范实际上煞费苦心地说不,那个符号没有定义outer
(甚至深入研究了实现如何在不违反其他规则的情况下避免它的细节)。它包含在第 13 节(在第 3 版和第 5 版规范中)。这是相关的高级报价:
可以从 FunctionExpression 的 FunctionBody 内部引用 FunctionExpression 中的标识符,以允许函数递归调用自身。但是,与 FunctionDeclaration 不同,FunctionExpression 中的标识符不能被引用,也不影响包含 FunctionExpression 的范围。
他们为什么要惹上这个麻烦?我不知道,但我怀疑这与在执行任何语句代码(分步代码)之前评估函数声明的事实有关,而函数表达式 - 像所有表达式一样 - 被评估为语句代码的一部分,当它们在控制流中到达时。考虑:
function foo() {
bar();
function bar() {
alert("Hi!");
}
}
当控制流进入函数foo
时,首先发生的事情之一是bar
函数被实例化并绑定到符号bar
;只有这样,解释器才会开始处理foo
函数体中的语句。这就是为什么调用bar
顶部有效的原因。
但在这儿:
function foo() {
var f;
f = function() {
alert("Hi!");
};
f();
}
函数表达式在到达时被评估(嗯,可能;我们不能确定某些实现不会更早地这样做)。不(或不应该)较早评估表达式的一个很好的原因是:
function foo() {
var f;
if (some_condition) {
f = function() {
alert("Hi (1)!");
};
}
else {
f = function() {
alert("Hi! (2)");
};
}
f();
}
...早点做会导致模棱两可和/或浪费精力。这导致了这里应该发生什么的问题:
function foo() {
var f;
bar();
if (some_condition) {
f = function bar() {
alert("Hi (1)!");
};
}
else {
f = function bar() {
alert("Hi! (2)");
};
}
f();
}
哪个bar
在开始时被调用?规范作者选择解决这种情况的方式是说根本bar
没有定义,因此完全回避了这个问题。(这不是他们可以解决的唯一方法,但这似乎是他们选择这样做的方式。)foo
那么 IE (JScript) 是如何处理的呢?被bar
调用者在开始时会提示“Hi (2)!”。结合我们知道两个函数对象是基于我们的其他测试创建的事实,最清楚地表明 JScript 将命名函数表达式处理为函数声明和函数表达式,因为这正是这里应该发生的事情:
function outer() {
bar();
function bar() {
alert("Hi (1)!");
}
function bar() {
alert("Hi (2)!");
}
}
我们有两个同名的函数声明。语法错误?你会这么认为,但事实并非如此。规范明确允许它,并说源代码顺序中的第二个声明“获胜”。从第 3 版规范的第 10.1.3 节:
对于代码中的每个 FunctionDeclaration,按源文本顺序,创建名称为 FunctionDeclaration 中的 Identifier 的变量对象的属性...如果变量对象已经具有该名称的属性,则替换其值和属性...
(“变量对象”是符号如何被解析;那是一个完整的“另一个主题”。)它在第 5 版(第 10.5 节)中同样明确,但是,嗯,可引用性要低得多。
所以它只是IE,那么?
需要明确的是,IE 并不是唯一拥有(或拥有)异常处理 NFE 的浏览器,尽管它们变得非常孤独(例如,一个相当大的 Safari 问题已得到修复)。只是 JScript 在这方面有一个很大的怪癖。但说到这一点,我认为它实际上是当前唯一存在任何真正大问题的主要实现——如果有人知道它们,有兴趣了解其他任何东西。
我们站在哪里
鉴于以上所有情况,目前我远离 NFE,因为我(像大多数人一样)必须支持 JScript。毕竟,使用函数声明并在稍后(或者实际上,更早)使用变量引用它很容易:
function foo() { }
var f = foo;
...并且跨浏览器可靠地工作,避免像您的问题这样的detachEvent
问题。其他合理的人以不同的方式解决问题,只是接受将创建两个函数并尝试将影响最小化,但我根本不喜欢这个答案,因为你发生了什么detachEvent
。