JavaScriptの関数の字句スコープ

JavaScript初心者向けの最近の投稿を読んだ後、著者の誰もまだ対処していない興味深い質問、つまりJavaScriptの関数の範囲に関する質問に関する小さなトピックを書くことにしました。



ウィキペディアによると:ECMAScriptの関数には語彙の範囲があります。 これは、関数が定義されたときにスコープが決定されることを意味します(関数が呼び出されたときにスコープが決定される動的スコープとは対照的です)。

実際、すべてが非常に簡潔かつ明確に書かれていますが、実際の例を見てみましょう。



var y = 5; var x = function(){ return y; }; var z = function(t){ var y = 10; return t(); }; z(x);
      
      





一部の読者にとっては、この結果が予期しないものになる可能性はありますが、結果として5番を取得します。 何が問題なのか見てみましょうが、最初は少し余談です。



JavaScriptでは、すべての関数はクロージャー(字句クロージャー)です。 これは、関数が実行可能コードのフラグメントだけでなく、このフラグメントが実行されるコンテキストでもあることを意味します。 JavaScriptでは、このコンテキストは一種のチェーンです。 チェーン内の最初のリンクは、すべてのローカル変数を含む関数呼び出しオブジェクトであり、関数が宣言されたコンテキスト内のオブジェクトへの参照です。次に、このオブジェクトは親オブジェクトを参照できます(親関数が別の関数内で宣言されている場合)などさらに。 私が話していることを理解しやすくするために、優れたイラストを提供します( ここから引用







この図は、変数の検索がどのように行われるかを示しています。 次のようになります。zは関数のローカル変数であり、呼び出しオブジェクトで直接検出されます。xは呼び出しオブジェクトではないため、検索はチェーンの上位で実行されます。 変数の検索は、グローバルオブジェクトまで実行されます;変数がその中に見つからない場合、結果は未定義になります。



これで、最初の例を詳細に解析する準備ができました。



 //    "y" = 5 var y = 5; //     , //      "" , //       . var x = function(){ //    "y". // ..   "y" , //       //    (  ) return y; }; //       , //       var z = function(t){ //      "y"=10, //     "y"=5 var y = 10; //    , //    . // ,        //      ! return t(); }; //   , //      "x"  . //      "x"! z(x);
      
      







例をもう一度読み直しましたが、まだ何も理解していないことに気付きました。 ここで何が起こっていますか? 前述のように、JavaScriptの関数はクロージャーです。つまり、宣言されたコンテキストを保存します。 したがって、関数を変数に保存すると、関数が宣言されたコンテキストを意図せずに保存します。これは、ローカル変数を含む呼び出しオブジェクトのチェーンであり、チェーン内の1レベル上のグローバルオブジェクトからのものです。



微妙な点が1つあります。 関数が宣言されたコンテキスト(可視領域のチェーン)は、大まかに言って、 オブジェクトのチェーンです 。 JavaScriptのオブジェクトはリンクとして保存されているため、これは理解することが非常に重要です。つまり、チェーンの各要素はプログラムの実行中に変更される可能性のある特定のオブジェクトを参照します(オブジェクトのコピーを参照します)。 つまり、スコープチェーンのオブジェクトは一部の外部関数によって変更できますが、すべての変更が利用可能であり、可視性のスコープのチェーンに可変オブジェクトが含まれる関数です。 つまり、関数の定義時に可視性のチェーンは固定されていますが、このチェーンで定義されているプロパティのリストは固定されていません。 スコープチェーンは変更される可能性があり、関数は実行時に存在するすべての要素にアクセスできます。 これは非常に重要な段落です。正確に理解したかどうかわからない場合は、もう一度お読みください。 それに書かれたすべてを理解したら、通常クロージャーと呼ばれる強力な効果を作成するための扉を開きます。



したがって、関数x()を実行しようとすると、次のことが起こります-JavaScriptインタープリターは、呼び出しオブジェクトでyという名前の変数を見つけようとしますが、それを見つけず、グローバルオブジェクトでの検索に切り替えます。 グローバルオブジェクトには、yという名前の変数が存在します。 関数宣言の直前に宣言しました。



関数xの可視性の連鎖は既に固定されているため、関数x()が実行されると変数の検索は宣言時に固定の範囲内で実行されるため、最終的にx()が実行される関数zのローカル変数の値はまだ使用されません機能。



結論の代わりに:上記の例が、語彙の範囲とは何か、そしてそれが動的とどのように異なるかを明確に示すことを望みます。



クロージャの美しさを実現し、関数の語彙的範囲を理解するために、別の小さな例を考えてみましょう。

 var y = 5; var x = function(){ return y; }; var z = function(t){ var y = 10; return t(); }; y = 15; z(x);
      
      







PSコメントでは、Habrの親愛なるユーザーjejeが興味深い例を挙げていますが、これも検討する価値があります。



 var y = 5; var x = function(){ return y; }; var z = function(t){ y = 10; //   ,      return t(); }; z(x);
      
      







この例は、z()関数内でvarキーワードなしでy変数が使用されるという点で元の例と異なります。 この例を実行した結果、出力番号10が得られます。なぜこれが起こっているのですか? 実際、z()関数内でローカル変数yを宣言するのではなく、グローバル変数を参照するためです。



この例では、JavaScriptでの変数の暗黙的な宣言の問題という非常に重要な問題が発生します。 関数内でキーワードvarなしで変数を使用すると、インタープリターは次の操作を実行します:変数は呼び出しオブジェクトで検索され(ローカル変数の検索)、変数が見つからない場合、検索はスコープチェーンの上位にあるオブジェクトで実行され、グローバルオブジェクトまで続きます。 オブジェクトで変数が見つかった場合、その値が変更されるため、グローバル変数へのアクセスが実行されます。 ただし、スコープチェーン内のオブジェクトで変数が見つからない場合、JavaScriptインタープリターは自動的に使用される変数を宣言して値を割り当て、 グローバルスコープで変数が作成されます。 このため、varキーワードなしで使用される関数変数には特別な注意を払う必要があります。 変数の明示的および暗黙的な宣言には、他の興味深い機能がありますが、これは別のマイクロトピックのトピックです。



All Articles