7

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

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

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

4

2 回答 2

6

好吧,它会起作用,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
  }
})();
于 2010-04-21T00:56:39.807 回答
6

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

区别:

这和你的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

于 2010-04-21T12:28:25.560 回答