7

私はしばらく前にこの質問をしましたが、受け入れられた答えに満足していました. しかし、私は今、次のテクニックに気づきました:

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

私が期待する結果を返します。私の最初の質問に対するTJCrowderの回答が正しければ、この手法は機能しないのではないでしょうか?

4

2 に答える 2

6

とても良い質問です。:-)

違い:

これとあなたの状況の違いdetachEventは、ここでは、「関数」の内側と外側の関数参照が同じであることを気にせず、コードが同じであることだけです。このdetachEvent状況では、「関数」の内側と外側で同じ関数参照が表示されることが重要detachEventでした。これは、指定した特定の関数を切り離すことで、そのように機能するためです。

2つの機能?

はい。CMSは、IE(JScript)がコード内のような名前付き関数式を検出すると、2つの関数を作成することを指摘しました。(これに戻ります。)興味深いのは、両方を呼び出していることです。はい、そうです。:-)最初の呼び出しは、式によって返される関数を呼び出し、次に、名前を使用するすべての呼び出しが他の関数を呼び出します。

コードを少し変更すると、これが少し明確になります。

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

f();最後に、関数式によって返された関数を呼び出しますが、興味深いことに、その関数は1回だけ呼び出されます。executeOnLoadそれ以外の場合は、参照を介して呼び出されると、呼び出されるのは他の関数です。ただし、2つの関数は両方とも同じデータ(testaroo変数を含む)を閉じ、同じコードを持っているため、効果は1つの関数しかないようなものです。ただし、CMSと同じように2つあることを示すことができます。

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によって作成されている2つの関数があります。

どうしたの?

基本的に、JScriptを実装する人々は、名前付き関数式を関数宣言と関数の両方として処理し、その過程で2つの関数オブジェクト(1つは「宣言」から、もう1つは「式」から)を作成することを決定したようです。異なる時間。これは完全に間違っていますが、それは彼らがしたことです。驚いたことに、IE8の新しいJScriptでさえこの動作を続けています。

そのため、コードは2つの異なる関数を認識(および使用)します。これは、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
    }
}

彼が述べたように、innerIE(JScript)で定義されていますが、他のブラウザーでは定義されていません。なぜだめですか?カジュアルな観察者には、2つの関数のことを除けば、関数名に関するJScriptの動作は正しいように見えます。結局のところ、関数だけがJavascriptに新しいスコープを導入しますよね?そして、inner関数はで明確に定義されていouterます。しかし、仕様は実際には「いいえ」と言うのに苦労しました。そのシンボルはで定義されていませんouter(実装が他のルールを破ることなくそれを回避する方法についての詳細まで掘り下げても)。セクション13(第3版と第5版の両方の仕様)で説明されています。関連する高レベルの見積もりは次のとおりです。

FunctionExpressionの識別子は、FunctionExpressionのFunctionBody内から参照して、関数がそれ自体を再帰的に呼び出すことができるようにすることができます。ただし、FunctionDeclarationとは異なり、FunctionExpressionの識別子は、FunctionExpressionを囲むスコープから参照することはできず、影響を与えることもありません。

なぜ彼らはこの問題に行きましたか?わかりませんが、関数宣言はステートメントコード(ステップバイステップコード)が実行される前に評価されるのに対し、関数 はすべての式と同様にステートメントコードの一部として評価されるという事実に関連していると思われます。 、制御フローで到達したとき。検討:

function foo() {

    bar();

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

制御フローが関数fooに入ると、最初に発生することの1つは、bar関数がインスタンス化されてシンボルにバインドされることbarです。その場合にのみ、インタプリタはfooの関数本体のステートメントの処理を開始します。barそのため、上部の呼び出しが機能します。

しかし、ここで:

function foo() {

    var f;

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

    f();
}

関数は、到達したときに評価されます(おそらく、一部の実装が以前にそれを実行しないかどうかはわかりません)。式が以前に評価されない(または評価されるべきではない)理由の1つは、次のとおりです。

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)!」と警告します。これは、他のテストに基づいて2つの関数オブジェクトが作成されることがわかっているという事実と相まって、JScriptが名前付き関数式を関数宣言および関数式として処理することを最も明確に示してます。

function outer() {

    bar();

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

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

同じ名前の2つの関数宣言があります。構文エラー?あなたはそう思うでしょうが、そうではありません。仕様は明らかにそれを許可しており、ソースコード順の2番目の宣言が「勝つ」と述べています。第3版仕様のセクション10.1.3から:

コード内のFunctionDeclarationごとに、ソーステキストの順序で、FunctionDeclarationの識別子という名前の変数オブジェクトのプロパティを作成します...変数オブジェクトにこの名前のプロパティが既にある場合は、その値と属性を置き換えます...

(「変数オブジェクト」は、シンボルがどのように解決されるかです。これはまったく別のトピックです。)第5版(セクション10.5)でも同様に明確ですが、割り当て可能性ははるかに低くなります。

では、IEだけですか?

明確にするために、NFEがかなり孤独になっているにもかかわらず(たとえば、かなり大きなSafariの問題が修正されています)、IEだけがNFEの異常な処理を行った(または行った)ブラウザーではありません。JScriptには、この点で非常に大きな癖があるだけです。しかし、実際には、これが本当に大きな問題を抱えている唯一の現在の主要な実装だと思います。誰かが知っている場合は、他の人のことを知りたいと思っています。

私たちが立っている場所

上記のすべてを考慮すると、私は(ほとんどの人と同じように)JScriptをサポートする必要があるため、今のところNFEには近づいていません。結局のところ、関数宣言を使用して、後で(または実際には以前に)変数を使用してそれを参照するのは簡単です。

function foo() { }
var f = foo;

...そしてそれはブラウザ間で確実に機能し、問題のようなdetachEvent問題を回避します。他の合理的な人々は、2つの関数が作成されることを受け入れ、影響を最小限に抑えようとするだけで、問題をの方法で解決しますが、で何が起こったのかという理由で、その答えはまったく好きではありませんdetachEvent

于 2010-04-21T12:28:25.560 に答える
6

JScript (IE) の問題点は、関数式 ( executeOnLoad) の識別子がその外側のスコープに漏れ、実際には2 つの関数オブジェクトが作成されることです。

(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 に答える