ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptを深く理解するシリーズ(15) 関数_javascriptスキル

JavaScriptを深く理解するシリーズ(15) 関数_javascriptスキル

WBOY
WBOYオリジナル
2016-05-16 17:54:26985ブラウズ

はじめに
この章では、非常に一般的な ECMAScript オブジェクトである関数に焦点を当て、さまざまなタイプの関数がコンテキスト変数オブジェクトと各関数のスコープ チェーンにどのような影響を与えるのか、およびその答えを詳しく説明します。以下のような質問があります: 以下で宣言された関数の間に違いはありますか? (もしそうなら、その違いは何ですか?)
原文: http://dmitrysoshnikov.com/ecmascript/chapter-5-functions/

コードをコピー コードは次のとおりです:

var foo = function () {
...
};

通常の方法:
コードをコピーします コードは次のとおりです:

function foo() {
.. .
}

または、次の関数が括弧で囲まれているのはなぜですか?
コードをコピー コードは次のとおりです:

(function () {
. ..
})();

具体的な導入については、第 12 章「変数オブジェクト」と第 14 章「スコープチェーン」を参照してください。上記 2 章の詳細。

しかし、関数の種類から始めて、1 つずつ見てみる必要があります。

関数の種類
ECMAScript には 3 つの関数の種類があります。関数 宣言、関数式、関数コンストラクターによって作成される関数。それぞれに独自の特徴があります。
関数宣言
関数宣言 (FD と略記) は、次のような関数です:
特定の名前を持つ
ソース コード内での場所: プログラム レベル (プログラム)レベル)、または他の関数の本体 (FunctionBody) 内で
コンテキストフェーズで
影響変数オブジェクト
を作成し、次の方法で宣言します
コードをコピーします コードは次のとおりです:

function exampleFunc() {
...
}

この関数タイプの主な特徴は、変数オブジェクト (つまり、コンテキストの VO に格納されているオブジェクト) にのみ影響することです。この機能は、2 番目の重要な点 (変数オブジェクト プロパティの結果) についても説明しています。これらは、コード実行フェーズ中にすでに利用可能です (コード実行前のコンテキスト フェーズに入ったときに FD がすでに VO に存在しているため)。
例 (関数は宣言前に呼び出されます)
コードをコピーします コードは次のとおりです:

foo();
function foo() {
alert('foo');
}

もう 1 つの重要な知識ポイントは、上記の 2 番目のポイントです。定義 - — ソース コード内の関数宣言の位置:
コードをコピー コードは次のとおりです:

// 関数 次の場所で宣言できます:
// 1) グローバル コンテキスト内で直接
function globalFD() {
// 2) または関数本体内でa function
function innerFD() {}
}

これら 2 つの位置のみで関数を宣言できます。つまり、式の位置または関数内で関数を定義することはできません。コードブロック。
関数宣言を置き換えるもう 1 つの方法は、関数式です。これについては次のように説明します。
関数式
関数式 (FE と省略) は関数です。
ソースに含める必要があります。コード 式の位置に表示されます
オプションの名前があります
変数オブジェクトには影響しません
コード実行フェーズ中に作成されます
この関数タイプの主な特徴は、常に次の形式で表現されることです。ソースコードのタイプの位置。最も単純な例は代入ステートメントです:
コードをコピーします コードは次のとおりです:

var foo = function () {
...
};

この例では、変数 foo に匿名関数式を代入し、その関数に名前を付けてアクセスできるようにする方法を示します。ふー——ふー()。
同時に、定義で説明したように、関数式にはオプションの名前を付けることもできます。
コードをコピー コードは次のとおりです:

var foo = function _foo() {
...
};

外部 FE は変数 "foo" を通じてアクセスされることに注意してください。 - —foo()、関数内 (再帰呼び出しなど) では、「_foo」という名前を使用できます。
FEに名前があったらFDと区別するのは難しいでしょう。ただし、定義を理解していれば、区別は単純かつ明確です。FE は常に式の位置にあります。次の例では、さまざまな ECMAScript 式を確認できます。
// 括弧 (グループ化演算子) には式
(function foo() {});
// 配列初期化子内には式 [function bar() {}];
// カンマは式
1, function baz() {};
式でのみ操作できます。式の定義に次のように記述されています。コード実行フェーズ中に作成され、変数オブジェクトには存在しません。動作の例を見てみましょう。

コード コードは次のとおりです。
// FE は定義フェーズの前には使用できません (コードの実行フェーズ中に作成されるため)
alert(foo); // "foo" は使用できません。 available Definition
(function foo() {});
// 変数オブジェクト VO
alert(foo); にないため、定義段階以降は使用できません。定義されていません
なぜ関数式が必要なのかという、かなりの数の疑問が生じます。答えは明らかです。式でこれらを使用しても、変数オブジェクトは「汚染されません」。最も単純な例は、関数をパラメータとして他の関数に渡すことです。
関数 foo(callback) {
callback();
}
foo(function bar() {
alert('foo.bar');
}); >foo(function baz() {
alert('foo.baz');
});


上記の例では、FE が変数に代入されています (つまり、パラメータ ) を使用すると、関数は次のように式をメモリに保存し、変数名を通じてアクセスします (変数は変数オブジェクトに影響するため)。


var foo = function () {
alert('foo');
foo(); 🎜>

別の例は、外部コンテキストから補助データを隠すためにカプセル化クロージャを作成することです (次の例では、作成直後に呼び出される FE を使用します):



コードをコピーします
コードは次のとおりです。 var foo = {} (functionInitialize() {
var; x = 10;
foo.bar = function () {
alert(x);
}); // 10; 🎜>alert( x); // "x" は未定義です


関数 foo.bar が ([[Scope]] 属性を通じて) の内部変数 "x" にアクセスしていることがわかります。関数の初期化。同時に、「x」は外部から直接アクセスできません。多くのライブラリでは、この戦略は「プライベート」データを作成し、補助エンティティを非表示にするために使用されます。このモードでは、初期化された FE の名前は通常無視されます:




コードをコピー


コードは次のとおりです: もう 1 つの例は、コード実行フェーズで条件付きステートメントを使用して FE を作成することです。変数オブジェクト VO を汚染しません。




コードをコピーします


コードは次のとおりです。
括弧に関する質問
記事の冒頭で述べた質問に戻りましょう - 「関数の直後に呼び出す場合、なぜ括弧で囲む必要があるのですか」作成しましたか?」の答えは、表現文の制限は次のとおりです。
標準によれば、式ステートメントはコード ブロックと区別することが難しいため、中括弧 { で始めることはできません。同様に、関数宣言と区別することが難しいため、関数キーワードで始めることもできません。つまり、作成直後にすぐに実行する関数を定義すると、次のように呼び出されます:


function () {
...
} ();
// 名前があっても
function foo() {
...
}();
上記 2 つの定義では関数宣言を使用しましたが、インタープリターは解釈時にエラーを報告しますが、多くの理由が考えられます。
グローバル コード (つまり、プログラム レベル) で定義されている場合、関数キーワードで始まるため、インタープリターはそれを関数宣言とみなします。最初の例では、関数の宣言であるため、SyntaxError が発生します。には名前がありません (関数宣言には名前が必要であると前述しました)。
2 番目の例では、foo という名前の関数宣言が通常どおり作成されていますが、それでも構文エラー (式がないとグループ化演算子エラー) が発生します。これは実際には、関数呼び出しで使用される括弧ではなく、関数宣言後のグループ化演算子です。したがって、次のコードを宣言するとします:
コード をコピーします。 コードは次のとおりです:

// "foo" は、コンテキストに入るときに作成される関数宣言です。
alert(foo); // 関数
function foo(x) {
alert(x)>}(1); ; // これは単なるグループ化演算子であり、関数呼び出しではありません。
foo(10); // これは実際の関数呼び出しであり、結果は 10 です。

宣言時に 2 つのオブジェクトが生成されるため、上記のコードには問題はありません。 、1 によるグループ化操作、上記の例は次のコードとして理解できます:

コードをコピー コードは次のとおりです。
// 関数宣言
function foo(x) {
alert(x);
}
// 式 1 を含むグループ化演算子

(1);

コードをコピーします コードは次のとおりです:
// 別の演算子には関数式が含まれています
(function () {});
// この演算子には式 "foo" も含まれています
("foo");// Wait


次のコードを定義した場合 (定義にはステートメントが含まれています)、定義があいまいであると言え、エラーが報告されます:
if (true) function foo () {alert (1)}
仕様によれば、上記のコードは間違っています (式ステートメントを function キーワードで始めることはできません)。しかし、次の例ではエラーが報告されません。なぜであるか考えてみてください。
インタプリタに「関数宣言の直後に呼び出したい」と伝えると、答えは非常に明確です。関数宣言ではなく関数式を宣言する必要があり、式を作成する最も簡単な方法はグループ化を使用するだけです。演算子括弧内に入れられるものは常に式であるため、インタプリタがそれを解釈するときにあいまいさはありません。この関数はコード実行フェーズ中に作成され、すぐに実行され、その後自動的に破棄されます (参照がない場合)。


(function foo(x) {
alert( x);
})(1); // これはグループ化演算子ではなく呼び出しです


上記のコードは、括弧で囲んだものです。通りすがり (1) 電話に出ます。
すぐに実行される次の関数の場合、括弧で囲む必要がないことに注意してください。関数はすでに式の位置にあり、パーサーは、実行中に作成される必要がある FE を処理していることを認識しているためです。関数実行フェーズ。関数が作成されると、その直後に関数が呼び出されます。


var foo = {
bar: function (x ) {
return x % 2 != 0 ? 'はい' : 'いいえ'
}(1)
}; // 'はい'


ご覧のとおり、foo.bar は関数ではなく文字列であり、ここでの関数は条件パラメーターに基づいてこのプロパティを初期化するためにのみ使用され、作成されてすぐに呼び出されます。
したがって、「括弧について」の質問に対する完全な答えは次のとおりです。関数が式の位置にない場合、グループ化演算子の括弧が必要です。つまり、関数を手動で FE に変換します。
パーサーが FE を扱っていることを認識している場合は、括弧を使用する必要はありません。
中括弧に加えて、次の形式でも関数を FE 型に変換できます。例:



コードをコピー コードは次のとおりです:

// 1 であることに注意してください。次のステートメントは
1, function () {
alert('匿名関数が呼び出されます')
; // または This
!function () {
alert('ECMAScript')
}();
// その他の手動変換フォーム
...

ただし、この例では括弧を使用するのが最も簡潔な方法です。
ちなみに、関数の説明を囲むグループ式には呼び出し括弧がないか、呼び出し括弧が含まれている可能性があります。つまり、次の 2 つの式はどちらも正しい FE です。
拡張機能の実装: 関数ステートメント
次のコードは、関数ステートメントに従って実行しないでください:

コードをコピー コードは次のとおりです。
if (true) {
function foo() {
alert(0);
}
} else {
function foo() {
alert(1);
}
}
foo(); // 実際、異なる環境でテストすると結果は異なります。

ここで注意する必要があるのは、この構文構造は標準によれば通常は正しくないということです。これは、関数宣言 (FD) がコード ブロック内に出現できないことも覚えているためです (ここでは if と else にはコード部分が含まれています)。 。かつて、FD はプログラム レベル (プログラム レベル) または他の関数本体に直接現れる 2 つの場所にのみ現れると述べました。
コード ブロックにはステートメントのみが含まれているため、これは不正確です。ブロック内で関数を使用できる唯一の場所は、これらのステートメントの 1 つ、つまり上ですでに説明した式ステートメント内です。ただし、定義上、中括弧 (コード ブロックとは異なるため) や関数キーワード (FD とは異なるため) で始めることはできません。
ただし、標準エラー処理の章では、プログラム構文の拡張実装が可能です。そのような拡張機能の 1 つは、コード ブロックに表示される関数です。この例では、既存のすべての実行は例外をスローせず、例外を処理します。しかし、彼らにはそれぞれ独自のやり方があります。
if-else 分岐ステートメントの出現は、動的な選択を意味します。つまり、論理的には、コード実行フェーズ中に動的に作成される関数式 (FE) である必要があります。ただし、ほとんどの実装では、コンテキストフェーズに入るときに単純に関数宣言 (FD) を作成し、最後に宣言された関数を使用します。つまり、関数 foo は「1」を表示し、実際には else 分岐は実行されません。
ただし、SpiderMonkey (および TraceMonkey) は、この状況を 2 つの方法で処理します。1 つは関数を宣言として処理しません (つまり、関数はコード実行フェーズ中の条件に基づいて作成されます)。一方、括弧で囲まれていないため (やはり解析エラー - 「FD とは異なる」)、それらは呼び出すことができないため、実際には関数式ではなく、変数オブジェクトに格納されます。
個人的には、この例では SpiderMonkey が正しく動作し、独自の関数中間型 (FE FD) を分割していると思います。これらの関数は条件に基づいて適切なタイミングで作成されますが、外部から呼び出すことができる FD と同様に、SpiderMonkey はこの構文拡張関数ステートメント (FS と略します) を呼び出します。この構文は MDC パスで説明されています。
名前付き関数式の特徴
関数式 FE に名前があると (名前付き関数式と呼ばれ、NFE と略します)、重要な特徴が現れます。 (上記の例からわかるように) 定義から、関数式はコンテキストの変数オブジェクトに影響を与えないことがわかります (つまり、関数を宣言する前に名前で関数を呼び出すことも、宣言した後に関数を呼び出すこともできません)。 。ただし、FE は再帰呼び出しで自分自身を名前で呼び出すことができます。


(function foo(bar) {
if ( bar) {
return;
}
foo(true); // "foo" は利用可能です
})()// 外部では利用できません
foo(); // "foo" は未定義です


「foo」はどこに保存されますか? foo のアクティブ オブジェクト内にありますか?いいえ、foo には「foo」が定義されていないためです。コンテキストの親変数オブジェクトに foo を作成しますか?いいえ、定義上、FE は VO (変数オブジェクト) に影響を与えないため、外部から foo を呼び出すときに実際にそれを見ることができます。それで、どこで?
重要なポイントは次のとおりです。インタプリタは、コード実行フェーズ中に名前付き FE を検出すると、FE が作成される前に、補助固有のオブジェクトを作成し、それを現在のスコープ チェーンの先頭に追加します。次に FE を作成し、その時点で (第 4 章のスコープ チェーンで学習したように) 関数は [[Scope]] 属性 (この関数コンテキストを作成したスコープ チェーン) を取得します。その後、FE の名前が一意の属性として特定のオブジェクトに追加され、この属性の値は FE への参照になります。最後のステップは、その特定のオブジェクトを親スコープ チェーンから削除することです。このアルゴリズムを疑似コードで見てみましょう:
コードをコピーします コードは次のとおりです:

特殊オブジェクト = {};
スコープ = 特殊オブジェクト スコープ;
foo = new FunctionExpression;
specialObject.foo = foo; ReadOnly }
delete Scope[0]; // 定義された特殊オブジェクトspecialObjectをスコープチェーンから削除します

したがって、この名前は関数の外では使用できません(親にないため)スコープ チェーン )、ただし、特定のオブジェクトは関数の [[scope]] に既に格納されており、名前はそこで利用可能です。
ただし、一部の実装 (Rhino など) では、このオプションの名前が特定のオブジェクトではなく FE のアクティベーション オブジェクトに格納されることに注意してください。 Microsoft での実装は FE ルールを完全に破り、関数が外部からアクセスできるように名前を親変数オブジェクトに保持します。
NFE と SpiderMonkey
NFE と SpiderMonkey の違いを見てみましょう。SpiderMonkey の一部のバージョンには、バグとして扱うことができる特定のオブジェクトに関連するプロパティがあります (ただし、標準によれば、すべてバグとして扱われます)。そのように実装されましたが、ECMAScript 標準のバグに近いものです)。これは識別子の解析メカニズムに関連しています。スコープ チェーンの分析は 2 次元であり、識別子の解析では、スコープ チェーン内の各オブジェクトのプロトタイプ チェーンも考慮されます。
Object.prototype でプロパティを定義し、「存在しない」変数を参照するとします。この実行メカニズムがわかります。したがって、以下の「x」解析例では、グローバル オブジェクトに到達しますが、「x」は見つかりません。ただし、SpiderMonkey では、グローバル オブジェクトは Object.prototype のプロパティを継承するため、「x」も解析できます。

コードをコピーします コードは次のとおりです。
Object.prototype.x = 10; 🎜>( function () {
alert(x); // 10
})();


アクティブなオブジェクトにはプロトタイプがありません。同じ開始条件に従って、上記の例では、内部関数のこの動作を確認することはできません。ローカル変数 "x" を定義し、内部関数 (FD または匿名 FE) を定義する場合は、内部関数で "x" を参照します。その後、この変数は Object.prototype ではなく親関数コンテキスト (つまり、解決されるべき場所) で解決されます。


コードをコピーします コードは次のとおりです。
Object.prototype.x = 10; 🎜>function foo() {
var x = 20;
// 関数宣言
function bar() {
alert(x);
bar(); / 20, foo
の変数オブジェクト AO からのクエリ // 無名関数式にも同じことが当てはまります
(function () {
alert(x); // 20, 変数オブジェクトからもクエリAO of foo
})();
}
foo();


ただし、一部の実装では、アクティブなオブジェクトのプロトタイプを設定します。したがって、Blackberry の実装では、上記の例の「x」は「10」として解釈されます。つまり、foo の値は Object.prototype で見つかっているため、foo のアクティブなオブジェクトには到達しません。
AO(バー FD または匿名 FE) -> いいえ ->
AO(バー FD または匿名 FE).[[プロトタイプ]] -> はい - 10
SpiderMonkey では、これと同じですこの状況は、FE という名前の特定のオブジェクトで完全に見ることができます。この特定のオブジェクトは (標準では) 通常のオブジェクトです - 「式 new Object() と同様」です。したがって、オブジェクトは Object.prototype からプロパティを継承する必要があります。これは、SpiderMonkey (バージョン 1.7 以降) の実行で見られるものとまったく同じです。残りの実行 (新しい TraceMonkey を含む) では、特定のオブジェクトのプロトタイプは設定されません。




コードをコピー
コードは次のとおりです:

function foo() {
var x = 10;
(function bar() {
alert(x); // 20、10 を超えない、foo からのアクティブなオブジェクトではない
// チェーンから取得した "x" が検索されます。
// AO(bar) - no -> __specialObject(bar) -> no
// __specialObject(bar).[[プロトタイプ]] - はい: 20
})();
Object.prototype.x = 20;


NFE および Jscript
現在の IE ブラウザ (JScript 5.8 — IE8 まで) に組み込まれている JScript の実行には、関数式 (NFE) に関連する多くのバグがあります。これらのバグはすべて ECMA-262-3 標準に完全に矛盾しており、一部は重大なエラーを引き起こす可能性があります。
まず第一に、この例の JScript は FE の主なルールに違反しています。関数名で変数オブジェクトに格納すべきではありません。オプションの FE 名は特定のオブジェクトに保存し、関数自体内でのみアクセスできるようにする必要があります (他の場所ではアクセスできません)。ただし、IE はそれを親変数オブジェクトに直接保存します。さらに、名前付き FE は、JScript では関数宣言 (FD) として扱われます。つまり、コンテキストに入る段階で作成され、ソースコードの定義より前にアクセスできるようになります。


// FE は変数オブジェクトに表示されます
testNFE ();
(function testNFE() {
alert('testNFE');
});
// 定義が完了した後も FE が表示されます
/ / 関数宣言と同じように
testNFE();


見てわかるように、これは完全にルールに違反しています。
次に、名前付き FE を宣言内の変数に代入するときに、JScript は 2 つの異なる関数オブジェクトを作成します。論理的には (特に、NFE の外部ではその名前にまったくアクセスしてはいけないことに注意してください)、この動作に名前を付けるのは困難です。


var foo = function bar() {
alert ('foo');
};
alert(typeof bar); // "function",
// 興味深いのは
alert(foo === bar); // false!
foo.x = 10;
alert(bar.x); // 実行しても結果は同じです
foo(); "
bar (); // "foo"


また見てください、めちゃくちゃです。
ただし、NFE を変数の代入とは別に (グループ演算子などを使用して) 記述し、それを変数に代入してその等価性をチェックすると、結果は true になることに注意することが重要です。オブジェクトでした。



コードをコピー コードは次のとおりです。 (function bar() {});
var foo = bar;
alert(foo === bar); // true
foo.x = 10;

>
現時点では解釈の余地があります。事実上、2 つのオブジェクトが再度作成されますが、実際には 1 つのオブジェクトのままです。もう一度考えてみると、ここでの NFE は FD として扱われ、コンテキスト フェーズに入るときに FD バーが作成されます。その後、コード実行フェーズ中に 2 番目のオブジェクト - 関数式 (FE) バーが作成されますが、これは保存されません。したがって、FE バーへの参照はなくなり、削除されます。このように、オブジェクトは FD bar だけであり、その参照は変数 foo に割り当てられます。
3 番目に、arguments.callee を介して関数を間接的に参照するという点で、アクティブ化されたオブジェクトの名前を参照します (正確に言うと、ここには 2 つの関数オブジェクトがあります。



コードをコピー
コードは次のとおりです。 var foo = function bar() { alert([
arguments. callee === foo,
arguments.callee === bar
}; // [true, false]
bar(); [false, true]


4 番目に、JScript は NFE を通常の FD のように扱います。条件式の規則に従いません。つまり、FD と同様に、コンテキストに入るときに NFE が作成されます。コードの最後の定義が使用されます


コードをコピーします

コードは次のとおりです:
var foo = function bar。 () { alert(1); }; if (false) { alert(2); } bar(); // 2
foo() // 1


この動作は「論理的に」説明することもできます。コンテキストフェーズに入ると、最後に検出された 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 foo() {
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. —
関数定義
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。