はじめに
第 12 章の変数オブジェクトの説明では、実行コンテキストのデータ (変数、関数宣言、関数パラメーター) が属性として変数オブジェクトに格納されることをすでに知っています。
同時に、変数オブジェクトがコンテキストに入るたびに作成され、値の更新がコード実行フェーズ中に発生することもわかります。
この章では、実行コンテキストに直接関連する詳細について説明することに専念しており、今回はスコープ チェーンというトピックについて説明します。
英語の原文: http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/
中国語の参考文献: http://www.denisdeng.com/?p=908
この記事内容の一部は上記のアドレスからのものですが、作者に感謝します
定義
その重要なポイントを簡単に説明して示したい場合、スコープ チェーンは主に内部関数に関連しています。 。
ECMAScript では内部関数の作成が可能であり、これらの関数を親関数から返すこともできることはわかっています。
var x = 10; foo() {
var y = 20;
function bar() {
alert(x y)>}
return bar;
foo()() ; // 30
このように、各コンテキストが独自の変数オブジェクトを持っていることは明らかです。グローバル コンテキストの場合、それは関数のグローバル オブジェクトそのものであり、それはアクティブ オブジェクトです。
スコープ チェーンは、内部コンテキスト内のすべての変数オブジェクト (親変数オブジェクトを含む) の単なるリストです。このチェーンは変数クエリに使用されます。つまり、上記の例では、「bar」コンテキストのスコープ チェーンには、AO(bar)、AO(foo)、および VO(global) が含まれます。
しかし、この問題を詳しく見てみましょう。
定義から始めて、例についてさらに詳しく説明しましょう。
スコープ チェーンは実行コンテキストに関連付けられ、変数オブジェクトのチェーンは識別子の解決における変数の検索に使用されます。
関数コンテキストのスコープ チェーンは、関数が呼び出されたときに作成されます。これには、この関数内のアクティブ オブジェクトと [[scope]] 属性が含まれます。以下では、関数の [[scope]] 属性について詳しく説明します。
はコンテキスト内で次のように表されます:
VO: {...}, // または AO
this: thisValue,
Scope: [ // スコープ チェーン
// すべての変数オブジェクトのリスト
/ / for 識別子の検索
]
};
スコープは次のように定義されます:
Scope = AO [[Scope]]
この共用体と識別子の解決プロセスは、以下で説明します。これは関数のライフサイクルに関連しています。
関数のライフサイクル
関数のライフサイクルは、作成フェーズとアクティブ化フェーズ (呼び出し時) に分かれています。詳しく見てみましょう。
関数の作成
ご存知のとおり、関数宣言はコンテキストに入るときに変数/アクティビティ (VO/AO) オブジェクトに配置されます。グローバル コンテキストでの変数と関数の宣言を見てみましょう (ここで、変数オブジェクトはグローバル オブジェクトそのものです。覚えていますよね?)
function foo() {
var y = 20; 🎜> }
foo(); // 30
関数がアクティブ化されると、正しい (期待される) 結果 - 30 が得られます。ただし、非常に重要な機能が 1 つあります。
以前は、現在のコンテキストに関連する変数オブジェクトについてのみ説明しました。ここでは、変数 "y" が関数 "foo" で定義されている (つまり、foo コンテキストの AO 内にある) ことがわかりますが、変数 "x" は "foo" コンテキストで定義されていないため、次のようになります。 「foo」の AO には追加されません。一見すると、変数「x」は関数「foo」に対してまったく存在しませんが、以下に示すように、「一見」しただけでは、「foo」コンテキストのアクティブなオブジェクトには次のものが含まれているだけであることがわかります。 1 つの属性 - 「y」。
コードをコピー
コードは次のとおりです: fooContext.AO = { y : 未定義 // 未定義 – コンテキストに入るとき – アクティブ化時 };
関数「foo」は変数「x」にどのようにアクセスしますか?理論的には、関数は上位レベルのコンテキストの変数オブジェクトにアクセスできる必要があります。実際、このメカニズムは関数内の [[scope]] 属性によって実装されます。
[[scope]] は、現在の関数コンテキストの上にあるすべての親変数オブジェクトの階層チェーンであり、関数の作成時に格納されます。
この重要な点に注意してください - [[scope]] は関数の作成時に保存され、関数が破棄されるまで静的 (不変) に永久に保存されます。つまり、関数を呼び出すことはできませんが、[[scope]] 属性が関数オブジェクトに書き込まれ、保存されています。
もう 1 つ考慮すべき点は、スコープ チェーンとは対照的に、[[scope]] はコンテキストではなく関数の属性であるということです。上記の例を考慮すると、関数 "foo" の [[scope]] は次のとおりです。
foo.[[Scope]] = [
globalContext.VO // === グローバル
]; たとえば、スコープと [[scope]] を表す通常の ECMAScript 配列を使用します。
続けて、関数を呼び出すとコンテキストが入り、このときアクティブオブジェクトが作成され、これとスコープ(スコープチェーン)が決定されることが分かります。この瞬間を詳しく考えてみましょう。
関数の起動
定義で述べたように、コンテキストを入力して AO/VO を作成した後、コンテキストの Scope 属性 (変数検索のスコープ チェーン) は次のように定義されます。 VO [ [Scope]]
上記のコードの意味は次のとおりです: アクティブなオブジェクトはスコープ配列の最初のオブジェクト、つまりスコープのフロントエンドに追加されます。
Scope = [AO].concat([[Scope]]);
この機能は識別子の解析処理にとって非常に重要です。
識別子の解決は、変数 (または関数宣言) がどの変数オブジェクトに属しているかを決定するプロセスです。
このアルゴリズムの戻り値には常に参照型があり、その基本コンポーネントは対応する変数オブジェクト (見つからない場合は null) で、属性名コンポーネントは検索された識別子の名前です。参照型の詳細については、第 13 章で説明します。
識別子の解決プロセスは、変数名に対応する属性の検索、つまり、最も深いコンテキストから開始してスコープ チェーンをバイパスして最上位までスコープ内の変数オブジェクトを継続的に検索することで構成されます。
このように、上向き検索では、コンテキスト内のローカル変数が親スコープ内の変数よりも優先されます。 2 つの変数の名前は同じですが、スコープが異なる場合、最初に見つかった変数が最も深いスコープにあります。
上で述べたことを説明するために、もう少し複雑な例を使用します。
コードをコピーします
関数 bar() {
var z = 30>
}
; }
foo(); // 60
このために、次の変数/アクティビティ、関数の [[scope]] 属性、およびコンテキストのスコープ チェーンがあります。 🎜>グローバルコンテキスト 変数オブジェクトは次のとおりです:
コードをコピーします
コードは次のとおりです:
globalContext.VO === Global = { x: 10 foo: の [[scope]] 属性「foo」が作成されるときの「foo」は次のとおりです:
コードをコピー
コードは次のとおりです:
foo.[[Scope]] = [
globalContext.VO ]; 「foo」がアクティブ化される (コンテキストに入る) と、「foo」のアクティブ オブジェクトコンテキストは:
コードをコピー
コードは次のとおりです:
コードをコピー
コードは次のとおりです:
内部関数 "bar" が作成されると、その [[スコープ] ]] は:
コードをコピー
コードは次のとおりです:
bar.[[Scope]] = [
fooContext.AO,
globalContext.VO
]; 「bar」がアクティブ化されると、「 「バー」コンテキストのアクティブなオブジェクトは次のとおりです:
z: 30
};
「bar」コンテキストのスコープ チェーンは次のとおりです:
コードをコピー
コードは次のとおりです: barContext.Scope = barContext.AO bar.[[Scope]] // つまり:
barContext .Scope = [
barContext.AO,
fooContext.AO,
globalContext.VO
]; 「x」、「y」、「」の識別子z" は次のように解析されます:
- "x"
-- barContext.AO // 見つかりません
-- fooContext.AO // 見つかりません
-- globalContext.VO // 見つかりました - 10
- "y"
-- barContext.AO // 見つかりません
-- fooContext.AO // 見つかりました - 20
- "z"
-- barContext.AO // found - 30
スコープの特性
スコープチェーンと関数 [[scope]] 属性に関連するいくつかの重要な機能を見てみましょう。
クロージャ
ECMAScript では、クロージャは関数の [[scope]] に直接関連しています。前述したように、[[scope]] は関数の作成時に保存され、関数とともに存続し消滅します。実際、クロージャは関数コードとその [[スコープ]] の組み合わせです。したがって、[[Scope]] には、関数内で作成された字句スコープ (親変数オブジェクト) がオブジェクトの 1 つとして含まれます。関数がさらにアクティブになると、変数オブジェクト (作成時に静的に保存される) のこの字句チェーン内で、より上位のスコープの変数が検索されます。
例:
コードをコピーします
(function () {
var x = 20;
foo(); // 10 not 20
})();
識別子の解決中に、関数の作成時に定義された字句スコープが使用されることがわかります。変数は 30 ではなく 10 に解決されます。さらに、この例では、関数 (この場合は関数 "foo" から返された匿名関数) の [[scope]] が、関数によって作成されたスコープが完了した後でも存続することも明確に示しています。
ECMAScript におけるクロージャの理論とその実行メカニズムの詳細については、第 16 章「クロージャ」を参照してください。
コンストラクターを通じて作成された関数の [[scope]]
上記の例では、関数の作成時に関数の [[scope]] 属性が取得され、すべての親コンテキストが取得されることがわかります。この属性変数を通じてアクセスされます。ただし、この規則には重要な例外があり、関数コンストラクターを通じて作成された関数がそれに関係します。
コードをコピーします
var barFE = function () { // 関数式
alert(x);
alert(y);
var barFn = Function('alert(x); alert(y);') ;
barFD(); // 10, 20
barFn() // 10, "y" が定義されていません
}
foo () ;
関数コンストラクターを通じて作成された関数「bar」が変数「y」にアクセスできないことがわかります。ただし、これは関数 "barFn" に [[scope]] 属性がないという意味ではありません (そうでない場合は変数 "x" にアクセスできません)。問題は、関数コンストラクターを介して作成された関数の [[scope]] 属性が常に唯一のグローバル オブジェクトであることです。これを念頭に置くと、このような関数を介してグローバル コンテキスト クロージャ以外のトップレベル コンテキスト クロージャを作成することはできません。
2 次元のスコープ チェーン検索
スコープ チェーンでの検索で最も重要な点は、ECMAScript のプロトタイプ機能から派生した、変数オブジェクト (存在する場合) の属性を考慮する必要があることです。プロパティがオブジェクト内で直接見つからない場合、クエリはプロトタイプ チェーンで続行されます。これは、2 次元連鎖検索と呼ばれることがよくあります。 (1) スコープ チェーン リンク、(2) 各スコープ チェーン - プロトタイプ チェーン リンクの奥深くまで進みます。プロパティが Object.prototype で定義されている場合、この効果を確認できます。
コードをコピー
コードは次のとおりです。
function foo() {
alert (x) ;
}
Object.prototype.x = 10; foo(); // 10 でわかるように、アクティブなオブジェクトにはプロトタイプがありません。以下の例:
コードをコピー
コードは次のとおりです:
関数 foo() {
var x = 20;
関数 bar() {
alert(x);
bar();
Object.prototype.x = 10;
foo(); // 20
関数 "bar" コンテキストのアクティブ化オブジェクトにプロトタイプがある場合、"x" はAO では直接解析されないため、Object .prototype に存在します。しかし、上記の最初の例では、識別子の解決中に、Object.prototype を継承するグローバル オブジェクト (一部の実装では必ずしもそうであるとは限りません) に到達するため、「x」は 10 に解決されます。
SpiderMokey の一部のバージョンでは、名前付き関数式 (略称 NFE) でも同じ状況が発生します。Blackberry では、特定のオブジェクトが Object.prototype から継承した関数式のオプションの名前を格納します。一部のバージョンでは、実行時のアクティベーションがobject は Object.prototype を継承します。ただし、この機能の詳細については、第 15 章の機能で説明します。
グローバルおよび eval コンテキストのスコープ チェーン
ここでは必ずしも興味深いものではありませんが、思い出してください。グローバル コンテキストのスコープ チェーンには、グローバル オブジェクトのみが含まれます。コード eval のコンテキストには、現在の呼び出しコンテキストと同じスコープ チェーンがあります。
globalContext.Scope = [
Global
];
evalContext.Scope ===callingContext.Scope;
コード実行中のスコープ チェーンへの影響
ECMAScript には、変更できるステートメントが 2 つあります。コード実行フェーズのスコープ チェーン中。これが with ステートメントと catch ステートメントです。これらはスコープ チェーンの先頭に追加され、オブジェクトはこれらの宣言に表示される識別子の中から検索する必要があります。これらのいずれかが発生した場合、スコープ チェーンは次のように簡単に変更されます:
Scope = withObject|catchObject AO|VO [[Scope]]
この例では、オブジェクトがパラメータとして追加されます (したがって、プレフィックスはありません) 、このオブジェクトのプロパティにアクセスできるようになります)。
var foo = {x: 10, y : 20} ;
with (foo) {
alert(x); // 10
alert(y); // 20
}
Scope = foo AO|VO [[Scope]]
再び、with ステートメントを通じて、オブジェクト内の識別子の解決がスコープ チェーンの先頭に追加されることがわかります。 🎜>
コードをコピーします
コードは次のとおりです。 var x = 10, y = 10; 🎜>with ({x: 20}) {
var x = 30, y = 30; // 30
alert(y); 🎜>alert(x); // 10
alert(y); // 30
コンテキストを入力するとどうなりますか?識別子「x」と「y」が変数オブジェクトに追加されました。さらに、コード実行フェーズ中に次の変更が行われます:
x = 10, y = 10;
オブジェクト {x:20} がスコープのフロントエンドに追加されます。 var ステートメントが出現します。もちろん、コンテキストに入るときにすべての変数が解析されて追加されているため、何も作成されません。
2 番目のステップでは、変数 "x" のみが変更されます。実際には、変数 "x" が変更されます。オブジェクトが解析され、ロールに追加されます。ドメイン チェーンのフロントエンドでは、「x」が 20 で、30 に変更されます。
変数オブジェクト「y」も変更され、その値は から変更されます。解析後の 10 ~ 30;
さらに、宣言が完了すると、その特定のオブジェクトがスコープ チェーンから削除されます (変更された変数 "x" - 30 もそのオブジェクトから削除されます)。スコープチェーンの構造が強化される前の状態に戻ります。
最後の 2 つのアラートでは、現在の変数オブジェクトの "x" は同じままですが、"y" の値は 30 に等しくなりますが、これは with ステートメントの実行中に変更されました。
同様に、catch ステートメントの例外パラメータにアクセスできるようになり、例外パラメータ名という 1 つの属性だけを持つ新しいオブジェクトが作成されます。図は次のようになります。
コードをコピーします
コードは次のとおりです。
スコープ チェーンは次のように変更されます:
コードをコピーします
コードは次のとおりです。
var catchObject = { 例: <例外オブジェクト> >}; スコープ = catchObject AO|VO [[スコープ]]
catch ステートメントの実行が完了すると、スコープ チェーンは以前の状態に復元されます。
結論
この段階で、実行コンテキストに関連するほぼすべての一般的な概念と、それらに関連する詳細について検討しました。計画どおり - 関数オブジェクトの詳細な分析: 関数タイプ (関数宣言、関数式) とクロージャ。ちなみに、この記事ではクロージャは [[scope]] 属性に直接関係していますが、それについては該当する章で説明します。コメント欄でご質問にお答えさせていただきます。
その他の参考資料