IE 中的命名函数表达式,第 2 部分

发布于 2024-08-30 00:31:09 字数 482 浏览 7 评论 0原文

我不久前问过这个问题并对接受的答案感到满意。然而,我现在才意识到以下技术:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

伴梦长久 2024-09-06 00:31:09

一个非常的好问题。 :-)

区别:

这与您的 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(); 调用函数表达式返回的函数,但有趣的是,该函数仅被调用一次< /em>。在所有其他情况下,当通过 executeOnLoad 引用调用它时,都会调用 other 函数。但由于这两个函数都关闭相同的数据(包括 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)!");
    }
}

我们有两个同名的函数声明。语法错误?你可能会这么想,但事实并非如此。规范明确允许这样做,并表示源代码顺序中的第二个声明“获胜”。来自第三版规范第 10.1.3 节:

对于代码中的每个 FunctionDeclaration,按照源文本顺序,创建名称为 FunctionDeclaration 中的 Identifier 的变量对象的属性...如果变量对象已有具有该名称的属性,则替换其值和属性...

(“变量对象”是符号的解析方式;这是一个完整的“另一个主题”。)它在第 5 版(第 10.5 节)中也同样明确,但是,嗯,可引用性要少得多。

那么这只是 IE 吗?

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

我们的立场

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

function foo() { }
var f = foo;

...并且可以跨浏览器可靠地工作,避免诸如 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 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.

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:

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

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

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:

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

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:

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

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.

But here:

function foo() {

    var f;

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

    f();
}

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

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

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

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:

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

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

function foo() { }
var f = foo;

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

晚雾 2024-09-06 00:31:09

好吧,它会起作用,JScript (IE) 的问题是函数表达式的标识符 (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
  }
})();

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

(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
  }
})();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文