IE 中的命名函数表达式,第 2 部分 [英] Named Function Expressions in IE, part 2

查看:29
本文介绍了IE 中的命名函数表达式,第 2 部分的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不久前问了这个问题,我很满意接受的答案.然而,我刚刚意识到以下技术:

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

返回我期望的结果.如果 TJCrowder 在我的第一个问题中的回答是正确的,那么这种技术不应该不起作用吗?>

returns the result I expect. If T.J.Crowder's answer from my first question is correct, then shouldn't this technique not work?

推荐答案

一个非常很好的问题.:-)

A very good question. :-)

这和你的detachEvent情况的区别在于,在这里,你不关心函数"内外的函数引用是否相同,只关心代码 是一样的.在 detachEvent 情况下,重要的是您在函数"内部和外部看到相同的函数引用,因为这就是 detachEvent 的工作方式,通过分离您提供的特定函数.

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 the detachEvent situation, it mattered that you see the same function reference inside and outside "the function" because that's how detachEvent works, by detaching the specific function you give it.

是的.CMS 指出,当 IE (JScript) 在您的代码中看到一个命名函数表达式时,它会创建两个函数.(我们会回到这一点.)有趣的是,您同时调用了两者.对真的.:-) 初始调用调用表达式返回的函数,然后所有使用该名称的调用都调用另一个.

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:

var testaroo = 0;
var f = function executeOnLoad() {
    if (testaroo++ < 5) {
        setTimeout(executeOnLoad, 25);
        return;
    }
    alert(testaroo); // alerts "6"
};
f();

最后的 f(); 调用了函数表达式返回的函数,但有趣的是,那个函数只被调用了一次.所有其他时候,当它通过 executeOnLoad 引用被调用时,它被调用的是 other 函数.但是由于这两个函数都关闭了相同的数据(其中包括 testaroo 变量)并且它们具有相同的代码,因此效果就像只有一个函数一样.不过,我们可以证明有两种方式,就像 CMS 所做的那样:

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 the executeOnLoad reference, it's the other function that gets called. But since the two functions both close over the same data (which includes the testaroo 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:

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 创建的函数.

This isn't just some artifact of the names, either, there really are two functions being created by JScript.

基本上,实现 JScript 的人显然决定将命名函数表达式两者处理为函数声明和函数表达式,创建两个函数对象在这个过程中(一个来自声明",一个来自表达")并且几乎可以肯定在不同的时间这样做.这是完全错误的,但这就是他们所做的.令人惊讶的是,即使是 IE8 中的新 JScript 也会继续这种行为.

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.

这就是您的代码看到(并使用)两个不同函数的原因.这也是CMS提到泄漏"名称的原因.转移到他的例子的稍微修改的副本:

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:

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中有明确的定义.但是规范实际上很痛苦地说不,那个符号notouter中定义(甚至深入研究实现如何避免它而不破坏其他规则).它在第 13 节(在第 3 版和第 5 版specs).这是相关的高级引用:

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 the inner function is clearly defined in outer. But the spec actually went to pains to say no, that symbol is not defined in outer (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:

可以从 FunctionExpression 的 FunctionBody 内部引用 FunctionExpression 中的 Identifier,以允许函数递归调用自身.但是,与 FunctionDeclaration 不同的是,FunctionExpression 中的 Identifier 不能被引用并且不会影响包含 FunctionExpression 的范围.

The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.

他们为什么要惹这个麻烦?我不知道,但我怀疑这与函数 声明 在执行任何语句代码(分步代码)之前求值有关,而函数 表达式 —喜欢所有的表达 —当在控制流中到达它们时,它们被评估为语句代码的一部分.考虑:

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:

function foo() {

    bar();

    function bar() {
        alert("Hi!");
    }
}

当控制流进入函数 foo 时,首先发生的事情之一是 bar 函数被实例化并绑定到符号 bar;只有这样解释器才会开始处理 foo 函数体中的语句.这就是在顶部调用 bar 的原因.

When the control flow enters function foo, one of the first things that happens is that the bar function is instantiated and bound to the symbol bar; only then does the interpreter start processing the statements in foo's function body. That's why the call to bar at the top works.

但在这里:

function foo() {

    var f;

    f = function() {
        alert("Hi!");
    };

    f();
}

函数 expression 在到达时被评估(好吧,可能;我们不能确定某些实现没有更早地执行).不(或不应该)更早评估表达式的一个很好的原因是:

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:

function foo() {

    var f;

    if (some_condition) {
        f = function() {
            alert("Hi (1)!");
        };
    }
    else {
        f = function() {
            alert("Hi! (2)");
        };
    }

    f();
}

...早做会导致模棱两可和/或浪费精力.这就引出了这里应该发生什么的问题:

...doing it earlier leads to ambiguity and/or wasted effort. Which leads to the question of what should happen here:

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 中定义,因此回避了这个问题完全.(这不是他们解决问题的唯一方式,但这似乎是他们选择的方式.)

Which bar gets called at the beginning? The way the specification authors chose to address that situation was to say that bar is not defined in foo 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.)

那么 IE (JScript) 是如何处理的呢?开头调用的 bar 提示Hi (2)!".这一点,结合我们知道两个函数对象是基于我们的其他测试创建的事实,最清楚地表明 JScript 将命名函数表达式处理为函数声明函数表达式,因为这正是 应该发生在这里:

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:

function outer() {

    bar();

    function bar() {
        alert("Hi (1)!");
    }

    function bar() {
        alert("Hi (2)!");
    }
}

我们有两个同名的函数声明.语法错误?你会这么认为,但事实并非如此.规范明确允许这样做,并说源代码顺序中的第二个声明获胜".来自第 3 版规范的第 10.1.3 节:

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:

对于代码中的每一个FunctionDeclaration,按照源文本顺序,创建一个名为FunctionDeclaration中的Identifier的变量对象的属性...如果变量对象已经有一个这个名字的属性,替换它的值和属性...

For each FunctionDeclaration in the code, in source text order, create a property of the variable object whose name is the Identifier in the FunctionDeclaration...If the variable object already has a property with this name, replace its value and attributes...

(变量对象"是符号的解析方式;那是一个完整的另一个话题".)它在第 5 版(第 10.5 节)中同样明确,但是,嗯,很少引用.

(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.

需要明确的是,IE 并不是唯一一个拥有(或曾经)异常处理 NFE 的浏览器,尽管它们变得非常孤独(例如,Safari 的一个大问题已得到修复).只是 JScript 在这方面有一个非常大的怪癖.但是说到这里,我认为它实际上是当前唯一一个有任何真正大问题的主要实现       有兴趣了解其他任何人,如果有人知道的话.

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.

鉴于上述所有情况,目前我远离 NFE,因为我(和大多数人一样)必须支持 JScript.毕竟,使用函数声明然后稍后(或实际上,更早)使用变量引用它是很容易的:

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:

function foo() { }
var f = foo;

...而且它可以跨浏览器可靠地工作,避免诸如 detachEvent 问题之类的问题.其他合理的人以不同的方式解决这个问题,只是接受将创建两个函数并试图将影响降至最低,但我根本不喜欢这个答案,因为 detachEvent 发生在你身上.

...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 with detachEvent.

这篇关于IE 中的命名函数表达式,第 2 部分的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆