この動作は「論理的に」説明することもできます。コンテキストフェーズに入ると、最後に検出された FD バーが作成されます。これは、alert(2) を含む関数です。その後、コード実行フェーズ中に、新しい関数 FE bar が作成され、それへの参照が変数 foo に割り当てられます。この foo のアクティブ化により、alert(1) が生成されます。ロジックは明確ですが、実行が明らかに壊れており、JScript のバグに依存しているため、IE のバグのため、「論理的に」という単語を引用符で囲みました。
JScript の 5 番目のバグは、グローバル オブジェクトのプロパティの作成に関連しています。グローバル オブジェクトは、修飾されていない識別子 (つまり、var キーワードなし) に代入することによって生成されます。ここでは NFE は FD として扱われるため、関数名が同じ場合には、変数オブジェクトに格納され、非修飾識別子 (つまり、変数ではなく、グローバル オブジェクトの通常のプロパティ) が割り当てられます。 as unqualified は同じ識別子を持っているため、プロパティはグローバルではありません。
(function () {
/ / var は必要ありません。その場合、それは現在のコンテキストの変数ではありません
// グローバル オブジェクトのプロパティです
foo = function foo() {}
});
// ただし、匿名関数の外側では、名前 foo は使用できません。
alert(typeof foo); // 未定義
「ロジック」はすでに明確です。 context フェーズでは、関数は foo を宣言します。匿名関数のローカル コンテキストのアクティブ オブジェクトを取得します。コードの実行フェーズでは、foo という名前は AO にすでに存在します。つまり、ローカル変数として扱われます。したがって、割り当て操作では、ECMA-262-3 のロジックに従ってグローバル オブジェクトの新しい属性を作成するのではなく、AO に既に存在する属性 foo が更新されるだけです。
関数コンストラクタで作成された関数
この種の関数オブジェクトにも独自の特徴があるため、FDやFEとは区別します。その主な特徴は、この関数の [[Scope]] 属性にはグローバル オブジェクトのみが含まれていることです。
var x = 10;
function foo() {
var x = 20;
var bar = new Function('アラート(x) ; アラート(y);');
bar() // 10, "y" は未定義です
}
[[スコープ] ] 関数バーのプロパティに foo コンテキストの Ao が含まれていません - 変数 'y' にアクセスできません。変数 'x' はグローバル オブジェクトから取得されます。ちなみに、Function コンストラクターは new キーワードの有無に関係なく使用できるため、これらのバリアントは同等です。
これらの関数のその他の機能は、
等価文法生成 および 結合オブジェクト に関連しています。仕様では、これらのメカニズムを最適化の推奨事項として提供しています (ただし、実装では最適化を使用しない場合があります)。たとえば、関数のループ内に 100 個の要素の配列がある場合、実行では結合オブジェクト メカニズムが使用される可能性があります。その結果、配列内のすべての要素に対して使用できる関数オブジェクトは 1 つだけになります。
var a = [];
for (var k = 0; k a[k] = function () {} // 結合されたオブジェクトを使用できます
}
しかし、関数コンストラクターを介して作成された関数は配線されません。
var a = [];
for (var k = 0; k a[k] = Function('') // 常に 100 個の異なる関数があります
}
結合オブジェクトに関連する別の例:
function bar(z) {
return z * z;
}
return bar;
var x = foo();
var y = foo();
ここでの実装は、関数 (内部 [[Scope]] プロパティを含む) は基本的に区別できないため、オブジェクト x とオブジェクト y を (同じオブジェクトを使用して) 接続する権利もあります。したがって、関数コンストラクターを通じて作成された関数は、常により多くのメモリ リソースを必要とします。
関数作成のアルゴリズム
以下の疑似コードは、関数作成のアルゴリズムを説明します (ユニオン オブジェクトに関連する手順を除く)。これらの説明は、ECMAScript の関数オブジェクトの詳細を理解するのに役立ちます。このアルゴリズムは、すべての関数タイプに適しています。
F = new NativeObject(); >
// 属性 [[Class]] は "Function"
F.[[Class]] = "Function"
// 関数オブジェクトのプロトタイプは Function
F.[[ Prototype]] = Function.prototype
// 関数自体が使用されます
// 式 F を呼び出すときに、[[Call]]
// をアクティブにして、新しい実行コンテキスト
F.[[Call]] = <関数への参照>
// オブジェクトの通常のコンストラクターでコンパイルします
// [[Construct]] 新しい実行コンテキストを介してアクティブ化します。キーワード
// そして、新しいオブジェクトにメモリを割り当てます
// 次に、F.[[Call]] を呼び出して、この
F.[[Construct]] = InternalConstructor
// 現在の実行コンテキスト スコープ チェーン
// たとえば、F のコンテキストを作成します。
F.[[Scope]] = activeContext.Scope
// 関数が new を通じて作成された場合Function(...),
// 次に
F.[[Scope]] = globalContext.Scope
//
F.length = countParameters
// F オブジェクト作成のプロトタイプ
__objectPrototype = new Object();
__objectPrototype.constructor = F // {DontEnum}、ループ内で列挙できません x
F.prototype = __objectPrototype
return F
F.[[Prototype]] は関数 (コンストラクター) のプロトタイプであり、F.prototype はこれによって作成されるオブジェクトのプロトタイプであることに注意してください。 function (用語が混乱を招くことが多いため、一部の記事では F.prototype を「コンストラクター プロトタイプ」と呼んでいますが、これは誤りです)。
結論
この記事は少し長いです。ただし、オブジェクトとプロトタイプに関する次の章で関数について引き続き説明するので、コメントでの質問に喜んでお答えします。
その他の参考資料
13. —
関数定義