IE 中的命名函数表达式,第 2 部分
我不久前问过这个问题并对接受的答案感到满意。然而,我现在才意识到以下技术:
var testaroo = 0;
(function executeOnLoad() {
if (testaroo++ < 5) {
setTimeout(executeOnLoad, 25);
return;
}
alert(testaroo); // alerts "6"
})();
返回我期望的结果。如果我第一个问题的 TJCrowder 的答案是正确的,那么这个技术不应该不起作用吗?
I asked this question a while back and was happy with the accepted answer. I just now realized, however, that the following technique:
var testaroo = 0;
(function executeOnLoad() {
if (testaroo++ < 5) {
setTimeout(executeOnLoad, 25);
return;
}
alert(testaroo); // alerts "6"
})();
returns the result I expect. If T.J.Crowder's answer from my first question is correct, then shouldn't this technique not work?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
一个非常的好问题。 :-)
区别:
这与您的
detachEvent
情况之间的区别在于,在这里,您不关心“函数”内部和外部的函数引用是否相同,只是代码相同。在 detachEvent 情况下,重要的是您在“函数”内部和外部看到相同的函数引用,因为这就是 detachEvent 的工作原理,通过分离您提供的特定函数。两个功能?
是的。 CMS 指出,当 IE (JScript) 看到类似于代码中的命名函数表达式时,它会创建两个函数。 (我们稍后会讨论这一点。)有趣的是,您正在调用它们两者。是的,真的。 :-) 初始调用调用表达式返回的函数,然后使用该名称的所有调用都调用另一个函数。
稍微修改一下代码可以使这一点更清晰:
最后的
f();
调用函数表达式返回的函数,但有趣的是,该函数仅被调用一次< /em>。在所有其他情况下,当通过executeOnLoad
引用调用它时,都会调用 other 函数。但由于这两个函数都关闭相同的数据(包括 testaroo 变量)并且它们具有相同的代码,因此效果非常像只有一个函数。不过,我们可以证明有两个函数,就像 CMS 所做的那样:这也不仅仅是名称的产物,JScript 创建了两个函数。
这是怎么回事?
基本上,实现 JScript 的人们显然决定将命名函数表达式两者作为函数声明和函数表达式进行处理,并在此过程中创建两个函数对象(一个来自“声明”,一个来自“表达”)并且几乎肯定是在不同的时间这样做的。这是完全错误的,但他们就是这样做的。令人惊讶的是,即使是 IE8 中的新 JScript 也延续了这种行为。
这就是为什么您的代码会看到(并使用)两个不同的函数。这也是CMS提到的“泄漏”这个名字的原因。转向他的示例的稍微修改的副本:
正如他提到的,
inner
是在 IE (JScript) 上定义的,但不是在其他浏览器上定义的。为什么不呢?对于不经意的观察者来说,除了这两个函数之外,JScript 在函数名称方面的行为似乎是正确的。毕竟,只有函数才能在 Javascript 中引入新的作用域,对吗?而inner
函数在outer
中明确定义。但规范实际上煞费苦心地说不,该符号没有在outer
中定义(甚至深入研究实现如何在不破坏其他内容的情况下避免它)规则)。第 13 节(第 3 版和第 5 版规范)。以下是相关的高层引用:他们为什么要这么麻烦呢?我不知道,但我怀疑这与以下事实有关:函数声明在执行任何语句代码(逐步代码)之前进行评估,而函数表达式 > ——像所有表达式一样——当在控制流中到达它们时,它们将作为语句代码的一部分进行计算。考虑一下:
当控制流进入函数
foo
时,首先发生的事情之一是bar
函数被实例化并绑定到符号bar
>;只有这样解释器才会开始处理 foo 函数体中的语句。这就是为什么调用顶部的bar
有效。但在这里:
函数表达式在到达时被评估(好吧,可能;我们不能确定某些实现不会更早地执行它)。该表达式不(或不应该)提前评估的一个很好的原因是:
……提前评估会导致歧义和/或浪费精力。这就引出了这里应该发生什么的问题:
哪个
bar
在开始时被调用?规范作者选择解决这种情况的方式是说bar
根本没有在foo
中定义,因此回避了这个问题完全。 (这不是他们解决这个问题的唯一方法,但这似乎是他们选择这样做的方法。)那么 IE (JScript) 如何处理这个问题呢?开始时调用的
bar
会发出警报“Hi (2)!”。结合我们知道根据其他测试创建了两个函数对象这一事实,最清楚地表明 JScript 将命名函数表达式处理为函数声明和函数表达式,因为这正是 应该发生在这里:我们有两个同名的函数声明。语法错误?你可能会这么想,但事实并非如此。规范明确允许这样做,并表示源代码顺序中的第二个声明“获胜”。来自第三版规范第 10.1.3 节:
(“变量对象”是符号的解析方式;这是一个完整的“另一个主题”。)它在第 5 版(第 10.5 节)中也同样明确,但是,嗯,可引用性要少得多。
那么这只是 IE 吗?
需要明确的是,IE 并不是唯一一个对 NFE 进行异常处理的浏览器,尽管它们变得非常孤独(例如,Safari 的一个相当大的问题已得到修复)。只是 JScript 在这方面有一个非常大的怪癖。但说到这一点,我认为它实际上是当前唯一一个存在重大问题的主要实现——如果有人知道的话,有兴趣了解其他任何实现。
我们的立场
鉴于上述所有情况,目前我远离 NFE,因为我(像大多数人一样)必须支持 JScript。毕竟,使用函数声明然后稍后(或者实际上更早)使用变量引用它是很容易的:
...并且可以跨浏览器可靠地工作,避免诸如
detachEvent
问题之类的问题。其他理性的人以不同的方式解决问题,只是接受将创建两个函数并试图将影响降到最低,但我根本不喜欢这个答案,因为detachEvent
确实发生在您身上。A very good question. :-)
The difference:
The difference between this and your
detachEvent
situation is that here, you don't care that the function reference inside and outside "the function" is the same, just that the code be the same. In thedetachEvent
situation, it mattered that you see the same function reference inside and outside "the function" because that's howdetachEvent
works, by detaching the specific function you give it.Two functions?
Yes. CMS pointed out that IE (JScript) creates two functions when it sees a named function expression like the one in your code. (We'll come back to this.) The interesting thing is that you're calling both of them. Yes, really. :-) The initial call calls the function returned by the expression, and then all of the calls using the name call the the other one.
Modifying your code slightly can make this a bit clearer:
The
f();
at the end calls the function that was returned by the function expression, but interestingly, that function is only called once. All the other times, when it's called via theexecuteOnLoad
reference, it's the other function that gets called. But since the two functions both close over the same data (which includes thetestaroo
variable) and they have the same code, the effect is very like there being just one function. We can demonstrate there are two, though, much the way CMS did:This isn't just some artifact of the names, either, there really are two functions being created by JScript.
What's going on?
Basically, the people implementing JScript apparently decided to process named function expressions both as function declarations and as function expressions, creating two function objects in the process (one from the "declaration," one from the "expression") and almost certainly doing so at different times. This is completely wrong, but it's what they did. Surprisingly, even the new JScript in IE8 continues this behavior.
That's why your code sees (and uses) two different functions. It's also the reason for the name "leak" that CMS mentioned. Shifting to a slightly modified copy of his example:
As he mentioned,
inner
is defined on IE (JScript) but not on other browsers. Why not? To the casual observer, aside from the two functions thing, JScript's behavior with regard to the function name seems correct. After all, only functions introduce new scope in Javascript, right? And theinner
function is clearly defined inouter
. But the spec actually went to pains to say no, that symbol is not defined inouter
(even going so far as to delve into details about how implementations avoid it without breaking other rules). It's covered in Section 13 (in both the 3rd and 5th edition specs). Here's the relevant high-level quote:Why did they go to this trouble? I don't know, but I suspect it relates to the fact that function declarations are evaluated before any statement code (step-by-step code) is executed, whereas function expressions — like all expressions — are evaluated as part of the statement code, when they're reached in the control flow. Consider:
When the control flow enters function
foo
, one of the first things that happens is that thebar
function is instantiated and bound to the symbolbar
; only then does the interpreter start processing the statements infoo
's function body. That's why the call tobar
at the top works.But here:
The function expression is evaluated when it's reached (well, probably; we can't be sure some implementations don't do it earlier). One good reason the expression isn't (or shouldn't be) evaluated earlier is:
...doing it earlier leads to ambiguity and/or wasted effort. Which leads to the question of what should happen here:
Which
bar
gets called at the beginning? The way the specification authors chose to address that situation was to say thatbar
is not defined infoo
at all, hence side-stepping the issue entirely. (It's not the only way they could have addressed it, but it seems to be the way they chose to do so.)So how does IE (JScript) process that? The
bar
called at the beginning alerts "Hi (2)!". This, combined with the fact we know two function objects are created based on our other tests, is the clearest indication that JScript processes named function expressions as function declarations and function expressions, because that's exactly what is supposed to happen here:There we have two function declarations with the same name. Syntax error? You'd think so, but it isn't. The specification clearly allows it, and says that the second declaration in source code order "wins." From Section 10.1.3 of the 3rd edition spec:
(The "variable object" is how symbols get resolved; that's a whole 'nother topic.) It's just as unambiguous in the 5th edition (Section 10.5), but, um, a lot less quotable.
So it's just IE, then?
Just to be clear, IE isn't the only browser that has (or had) unusual handling of NFEs, although they're getting pretty lonely (a pretty big Safari issue has been fixed, for instance). It's just that JScript has a really big quirk in this regard. But come to that, I think it actually is the only current major implementation with any really big issue — be interested to know of any others, if anyone knows of them.
Where we stand
Given all of the above, for the moment, I stay away from NFEs because I (like most people) have to support JScript. After all, it's easy enough to use a function declaration and then refer to it later (or indeed, earlier) with a variable:
...and that works reliably across browsers, avoiding issues like your
detachEvent
problem. Other reasonable people solve the problem differently, just accepting that two functions will get created and trying to minimize the impact, but I don't like that answer at all because of exactly what happened to you withdetachEvent
.好吧,它会起作用,JScript (IE) 的问题是函数表达式的标识符 (
executeOnLoad
) 将泄漏到其封闭范围,并实际上创建两个函数对象..Well, it will work, the problem with JScript (IE), is that the identifier of the function expression (
executeOnLoad
) will leak to its enclosing scope, and actually creating two function objects..