スコープは JavaScript の最も重要な概念の 1 つです。JavaScript をよく学びたい場合は、JavaScript のスコープとスコープ チェーンがどのように機能するかを理解する必要があります。今日の記事では、JavaScript のスコープとスコープ チェーンについて簡単に紹介し、誰もが JavaScript をより良く学習できるようにしたいと考えています。
JavaScriptのスコープ
どのプログラミング言語にもスコープという概念があります。簡単に言えば、スコープは変数と関数のアクセス可能な範囲です。つまり、スコープは変数と関数の可視性とライフサイクルを制御します。 JavaScript には、グローバル スコープとローカル スコープの 2 種類の変数スコープがあります。
1. グローバルな範囲
コード内のどこからでもアクセスできるオブジェクトには、グローバル スコープがあります。一般に、次の状況ではグローバル スコープがあります。
(1) 最も外側の関数と、最も外側の関数の外側で定義された変数は、グローバル スコープを持ちます。次に例を示します。
次のようにコードをコピーします。
varauthorName="マウンテンサイド ストリーム";
functiondoSomething(){
varblogName="";
functioninnerSay(){
アラート(ブログ名);
}
innerSay();
}
alert(authorName);//マウンテンサイドクリーク
alert(blogName);//スクリプトエラー
doSomething();//
innerSay()//スクリプトエラー
(2) 定義されておらず、直接割り当てられていないすべての変数は、グローバル スコープを持つように自動的に宣言されます。次に例を示します。
次のようにコードをコピーします。
functiondoSomething(){
varauthorName="マウンテンサイド ストリーム";
ブログ名="";
アラート(作成者名);
}
アラート(ブログ名);//
alert(authorName);//スクリプトエラー
変数 blogName にはグローバル スコープがありますが、関数の外部から authorName にアクセスすることはできません。
(3) ウィンドウ オブジェクトのすべてのプロパティはグローバル スコープを持つ
一般に、ウィンドウ オブジェクトの組み込みプロパティには、window.name、window.location、window.top などのグローバル スコープがあります。
2. ローカルスコープ
グローバル スコープとは対照的に、ローカル スコープは通常、コードの固定部分内 (最も一般的には関数内) 内でのみアクセスできるため、場所によっては、次のコードのように、このスコープを関数スコープと呼ぶ人も見かけます。 blogName と関数 innerSay はローカル スコープのみを持ちます。
次のようにコードをコピーします。
functiondoSomething(){
varblogName="";
functioninnerSay(){
アラート(ブログ名);
}
innerSay();
}
alert(blogName);//スクリプトエラー
innerSay();//スクリプトエラー
スコープチェーン
JavaScript では、関数もオブジェクトです。実際、JavaScript 内のすべてのものはオブジェクトです。関数オブジェクトには、他のオブジェクトと同様に、コードを通じてアクセスできるプロパティと、JavaScript エンジンのみがアクセスできる一連の内部プロパティがあります。内部プロパティの 1 つは [[Scope]] で、ECMA-262 標準第 3 版で定義されています。この内部プロパティには、関数が作成されるスコープ内のオブジェクトのコレクションが含まれます。このコレクションは関数のスコープ チェーンと呼ばれます。 、関数がどのデータにアクセスできるかを決定します。
関数が作成されると、そのスコープ チェーンには、関数が作成されたスコープからアクセスできるデータ オブジェクトが設定されます。たとえば、次の関数を定義します。
次のようにコードをコピーします。
functionadd(num1,num2){
varsum=num1+num2;
返品合計;
}
関数 add が作成されると、次の図に示すように、そのスコープ チェーンはすべてのグローバル変数を含むグローバル オブジェクトで埋められます (注: この図はすべての変数の一部のみを示しています)。
関数 add のスコープは実行時に使用されます。たとえば、次のコードを実行します。
次のようにコードをコピーします。
var total = add(5,10);
この関数が実行されると、「実行コンテキスト」と呼ばれる内部オブジェクトが作成され、関数が実行される環境が定義されます。各ランタイム コンテキストには、識別子解決のための独自のスコープ チェーンがあり、ランタイム コンテキストが作成されると、そのスコープ チェーンは、現在実行中の関数の [[Scope]] に含まれるオブジェクトに初期化されます。
値は、関数内に出現する順序でランタイム コンテキストのスコープ チェーンにコピーされます。これらは一緒に「アクティベーション オブジェクト」と呼ばれる新しいオブジェクトを形成します。このオブジェクトには、すべてのローカル変数、名前付きパラメータ、パラメータ コレクションが含まれており、実行時コンテキストがスコープ チェーンのフロント エンドにプッシュされます。が破棄されると、アクティブなオブジェクトも破棄されます。新しいスコープ チェーンを以下に示します。
関数の実行中に変数が見つからない場合は、識別子の解決プロセスを経て、データを取得して保存する場所が決定されます。このプロセスはスコープ チェーンの先頭、つまりアクティブなオブジェクトから開始され、同じ名前の識別子が見つかった場合は、その識別子に対応する変数を使用します。見つからない場合は、続行します。スコープチェーン内の次のオブジェクトを検索します。 If 検索後にオブジェクトが見つからない場合、識別子は未定義とみなされます。関数実行時には、各識別子に対してこのような検索処理が行われます。
スコープチェーンとコードの最適化
スコープ チェーンの構造から、識別子がランタイム コンテキストのスコープ チェーンの奥深くに位置するほど、読み取りおよび書き込みの速度が遅くなることがわかります。上の図に示すように、グローバル変数は常にランタイム コンテキスト スコープ チェーンの最後に存在するため、識別子の解決中にグローバル変数を見つけるのが最も遅くなります。したがって、コードを記述するときは、グローバル変数の使用をできるだけ少なくし、ローカル変数をできるだけ使用する必要があります。経験則としては、クロススコープ オブジェクトが複数回参照される場合は、使用する前にローカル変数に格納します。たとえば、次のコード:
次のようにコードをコピーします。
functionchangeColor(){
document.getElementById("btnChange").onclick=function(){
document.getElementById("targetCanvas").style.backgroundColor="red";
};
}
この関数はグローバル変数ドキュメントを 2 回参照し、変数を見つけるには、グローバル オブジェクトで最終的に見つかるまでスコープ チェーン全体をたどる必要があります。このコードは次のように書き換えることができます。
次のようにコードをコピーします。
functionchangeColor(){
vardoc=ドキュメント;
doc.getElementById("btnChange").onclick=function(){
doc.getElementById("targetCanvas").style.backgroundColor="red";
};
}
このコードは比較的単純であり、書き換え後のパフォーマンスの大幅な向上は見られませんが、繰り返しアクセスされるプログラム内に多数のグローバル変数がある場合、書き換えられたコードのパフォーマンスは大幅に向上します。
スコープチェーンを変更する
対応するランタイム コンテキストは関数が実行されるたびに一意であるため、同じ関数を複数回呼び出すと、関数の実行が完了すると複数のランタイム コンテキストが作成されます。各ランタイム コンテキストはスコープ チェーンに関連付けられます。通常の状況では、ランタイム コンテキストの実行中に、そのスコープ チェーンは with ステートメントと catch ステートメントによってのみ影響を受けます。
with ステートメントは、オブジェクトを使用してコードを繰り返し記述することを避けるためのショートカット方法です。例えば:
次のようにコードをコピーします。
functioninitUI(){
with(ドキュメント){
varbd=本体、
links=getElementsByTagName("a"),
i=0、
len=リンクの長さ;
while(i<len){
update(リンク[i++]);
}
getElementById("btnInit").onclick=function(){
doSomething();
};
}
}
ここでは、width ステートメントを使用してドキュメントを複数回記述することを回避しています。これにより効率が向上しているように見えますが、実際にはパフォーマンスの問題が発生します。
コードが with ステートメントに到達すると、ランタイム コンテキストのスコープ チェーンが一時的に変更されます。パラメーターで指定されたオブジェクトのすべてのプロパティを含む新しい可変オブジェクトが作成されます。このオブジェクトはスコープ チェーンの先頭にプッシュされます。つまり、関数のすべてのローカル変数が 2 番目のスコープ チェーン オブジェクト内にあるため、アクセスコストが高くなります。以下に示すように:
したがって、この例では、ドキュメントをローカル変数に保存するだけでパフォーマンスが向上する可能性があるため、プログラム内で with ステートメントを使用することは避けてください。
スコープ チェーンを変更するもう 1 つの点は、try-catch ステートメント内の catch ステートメントです。 try コード ブロックでエラーが発生すると、実行プロセスは catch ステートメントにジャンプし、その後、例外オブジェクトが可変オブジェクトにプッシュされ、スコープの先頭に配置されます。 catch ブロック内では、関数のすべてのローカル変数が 2 番目のスコープ チェーン オブジェクトに配置されます。サンプルコード:
次のようにコードをコピーします。
試す{
doSomething();
}キャッチ(元){
alert(ex.message);//ここでスコープチェーンが変更されます
}
catch ステートメントが実行されると、スコープ チェーンは以前の状態に戻ることに注意してください。 try-catch ステートメントはコードのデバッグや例外処理に非常に役立つため、完全に回避することはお勧めできません。コードを最適化することで、catch ステートメントのパフォーマンスへの影響を軽減できます。良いパターンは、エラー処理を関数に委任することです。次に例を示します。
次のようにコードをコピーします。
試す{
doSomething();
}キャッチ(元){
handleError(ex);//プロセッサメソッドに委任する
}
最適化されたコードでは、catch 句で実行されるコードは handleError メソッドだけです。この関数は例外オブジェクトをパラメータとして受け取るため、エラーをより柔軟かつ均一に処理できます。実行されるステートメントは 1 つだけであり、ローカル変数にはアクセスされないため、スコープ チェーンの一時的な変更はコードのパフォーマンスに影響しません。