ホームページ > 記事 > ウェブフロントエンド > JavaScript スコープ チェーン パート 3: スコープ チェーンの特性
スコープチェーンと関数 [[scope]] 属性に関連するいくつかの重要な機能を見てみましょう。
クロージャ
ECMAScript では、クロージャは関数の [[scope]] に直接関連しています。前述したように、[[scope]] は関数の作成時に保存され、関数とともに存続し、消滅します。実際、クロージャは関数コードとその [[スコープ]] の組み合わせです。したがって、[[Scope]] には、関数内で作成された字句スコープ (親変数オブジェクト) がオブジェクトの 1 つとして含まれます。関数がさらにアクティブ化されると、変数オブジェクト (作成時に静的に保存される) のこの字句チェーン内で、より上位のスコープの変数が検索されます。例:
var x = 10; function foo() { alert(x); } (function () { var x = 20; foo(); // 10, but not 20 })();
関数の作成時に定義された字句スコープを使用して、識別子の解決中に変数が 30 ではなく 10 に解決されることが再度わかります。さらに、この例では、関数 (この場合は関数 "foo" から返された匿名関数) の [[scope]] が、関数によって作成されたスコープが完了した後でも存続することも明確に示しています。
ECMAScript のクロージャ理論とその実行メカニズムの詳細については、後ほど説明します。
コンストラクターを通じて作成された関数の [[scope]]
上の例では、関数の作成時に関数の [[scope]] 属性が取得され、親のすべての変数が取得されることがわかります。コンテキストには、この属性を通じてアクセスできます。ただし、この規則には重要な例外があり、関数コンストラクターを通じて作成された関数がそれに関係します。
var x = 10; function foo() { var y = 20; function barFD() { // 函数声明 alert(x); alert(y); } var barFE = function () { // 函数表达式 alert(x); alert(y); }; var barFn = Function('alert(x); alert(y);'); barFD(); // 10, 20 barFE(); // 10, 20 barFn(); // 10, "y" is not defined } foo();
関数コンストラクターを通じて作成された関数「bar」が変数「y」にアクセスできないことがわかります。ただし、これは関数 "barFn" に [[scope]] 属性がないという意味ではありません (そうでない場合は変数 "x" にアクセスできません)。問題は、関数コンストラクターを介して作成された関数の [[scope]] 属性が常に唯一のグローバル オブジェクトであることです。これを念頭に置くと、このような関数を使用してグローバル コンテキスト クロージャ以外のトップレベル コンテキスト クロージャを作成することはできません。
二次元スコープ チェーン検索
スコープ チェーンでの検索で最も重要な点は、ECMAScript のプロトタイプ機能から派生した、変数オブジェクト (存在する場合) のプロパティを考慮する必要があることです。プロパティがオブジェクト内で直接見つからない場合、クエリはプロトタイプ チェーンで続行されます。これは、2 次元連鎖検索と呼ばれることがよくあります。 (1) スコープ チェーン リンク、(2) 各スコープ チェーン - プロトタイプ チェーン リンクの奥深くまで進みます。プロパティが Object.prototype で定義されている場合、この効果を確認できます。
function foo() { alert(x); } Object.prototype.x = 10; foo(); // 10
アクティブ オブジェクトにはプロトタイプがありません。次の例でわかります:
function foo() { var x = 20; function bar() { alert(x); } bar(); } Object.prototype.x = 10; foo(); // 20
関数 "bar" コンテキストのアクティブ化オブジェクトにプロトタイプがある場合、"x" は Object.prototype にあるため解決されます。 AO では直接解析されません。しかし、上記の最初の例では、識別子の解決中に、Object.prototype を継承するグローバル オブジェクト (一部の実装では必ずしもそうであるとは限りません) に到達するため、「x」は 10 に解決されます。
同じ状況が、SpiderMokey の一部のバージョンの名前付き関数式 (NFE と略される) でも発生します。特定のオブジェクトには、Object.prototype から継承された関数式のオプションの名前が格納されます。Blackberry バージョンの一部のバージョンでは、実行時間アクティブ化オブジェクトは Object.prototype から継承します。
グローバルおよび 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; alert(x); // 30 alert(y); // 30 } alert(x); // 10 alert(y); // 30
在进入上下文时发生了什么?标识符“x”和“y”已被添加到变量对象中。此外,在代码运行阶段作如下修改:
x = 10, y = 10;
对象{x:20}添加到作用域的前端;
在with内部,遇到了var声明,当然什么也没创建,因为在进入上下文时,所有变量已被解析添加;
在第二步中,仅修改变量“x”,实际上对象中的“x”现在被解析,并添加到作用域链的最前端,“x”为20,变为30;
同样也有变量对象“y”的修改,被解析后其值也相应的由10变为30;
此外,在with声明完成后,它的特定对象从作用域链中移除(已改变的变量“x”--30也从那个对象中移除),即作用域链的结构恢复到with得到加强以前的状态。
在最后两个alert中,当前变量对象的“x”保持同一,“y”的值现在等于30,在with声明运行中已发生改变。
同样,catch语句的异常参数变得可以访问,它创建了只有一个属性的新对象--异常参数名。图示看起来像这样:
try { ... } catch (ex) { alert(ex); }
作用域链修改为:
var catchObject = { ex: <exception object> }; Scope = catchObject + AO|VO + [[Scope]]
在catch语句完成运行之后,作用域链恢复到以前的状态。
结论
在这个阶段,我们几乎考虑了与执行上下文相关的所有常用概念,以及与它们相关的细节。按照计划--函数对象的详细分析:函数类型(函数声明,函数表达式)和闭包。顺便说一下,在这篇文章中,闭包直接与[[scope]]属性相关,但是,关于它将在合适的篇章中讨论。
以上就是JavaScript作用域链其三:作用域链特征的内容,更多相关内容请关注PHP中文网(www.php.cn)!