我前一段时间问了这个问题,并对接受的答案感到满意.然而,我刚才意识到以下技术:
var testaroo = 0; (function executeOnLoad() { if (testaroo++ < 5) { setTimeout(executeOnLoad, 25); return; } alert(testaroo); // alerts "6" })();
返回我期望的结果.如果TJCrowder从我的第一个问题的回答是正确的,那么这种技术不应该起作用吗?
好吧,它可以工作,JScript(IE)的问题是函数expression(executeOnLoad
)的标识符将泄漏到其封闭范围,并实际创建两个函数对象.
(function () { var myFunc = function foo () {}; alert(typeof foo); // "undefined" on all browsers, "function" on IE if (typeof foo !== "undefined") { // avoid TypeError on other browsers alert( foo === myFunc ); // false!, IE actually creates two function objects } })();
一个非常好的问题.:-)
这与你的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
一开始的警报称为"喜(2)!".这与我们知道基于我们的其他测试创建的两个函数对象的事实相结合,是JScript将命名函数表达式作为函数声明和函数表达式处理的最明确的指示,因为这正是应该发生的事情:
function outer() { bar(); function bar() { alert("Hi (1)!"); } function bar() { alert("Hi (2)!"); } }
我们有两个具有相同名称的函数声明.语法错误?你是这么认为的,但事实并非如此.规范明确允许它,并说源代码顺序中的第二个声明"胜出".从第3版规范的第10.1.3节:
对于代码中的每个FunctionDeclaration,在源文本顺序中,创建变量对象的属性,其名称是FunctionDeclaration中的标识符...如果变量对象已经具有此名称的属性,则替换其值和属性...
("变量对象"是符号得到解决的方式;这是一个完整的"其他主题".)它在第5版(第10.5节)中同样明确,但是,嗯,更不用引用了.
需要明确的是,IE并不是唯一拥有(或曾经)不寻常处理NFE的浏览器,尽管它们变得相当孤独(例如,已经修复了一个非常大的Safari问题).只是JScript 在这方面有一个非常大的怪癖.但是,我认为它实际上是目前唯一一个存在任何重大问题的主要实施方式 - 如果有人知道的话,有兴趣了解其他任何问题.
鉴于以上所有,目前,我远离NFE,因为我(像大多数人一样)必须支持JScript.毕竟,使用函数声明然后使用变量稍后(或实际上,更早)引用它是很容易的:
function foo() { } var f = foo;
......并且可以跨浏览器可靠地工作,避免问题等detachEvent
问题.其他合理的人以不同的方式解决问题,只是接受将创建两个函数并尝试最小化影响,但我完全不喜欢这个答案,因为你究竟发生了什么detachEvent
.