javascriptの実行文脈を深く理解する(execution context)

10326 ワード

この文章では、実行文脈とJavascriptの中の最も基本的なものも最も重要な概念です.この文章を読んだら、Javascriptエンジンの内部はコードを実行する前にいったい何をしていたのかが分かります.なぜいくつかの関数と変数が声明されていない前に使用されることができますか?そしてそれらの最終値はどのように定義されていますか?
実行文脈は何ですか?
Javascriptのコードの運行環境は以下の3つに分けられます.
  • グローバルクラスのコードです.これはデフォルトのコード実行環境です.コードが読み込まれると、エンジンが最初に入ったのはこの環境です.
  • 関数レベルのコード–関数を実行すると、関数体のコードが実行されます.
  • Evalのコード–Eval関数内で実行されるコード.
  • インターネットでは、機能領域を説明する多くのリソースを見つけることができます.この文を理解しやすくするために、「実行コンテキスト」を現在のコードの実行環境または作用領域と見なしてもいいです.以下では、グローバルおよび関数レベルの実行コンテキストを含む例を示します.
    上の図では、全部で4つのコンテキストを実行します.紫は全体の文脈を表します.緑はperson関数内のコンテキストを表します.青とオレンジは、person関数内の別の2つの関数の文脈を表します.どのような状況でも、一つの大域の文脈だけが存在し、この文脈は他の任意の文脈によってアクセスできることに注意してください.つまり、personのコンテキストでグローバルコンテキストにおけるsayHello変数にアクセスできます.もちろん関数firstNameやlastNameでも同様に変数にアクセスできます.
    関数コンテキストの個数には制限がありません.個々の関数を実行すると、エンジンは自動的に関数コンテキストを作成します.言い換えれば、ローカルスコープを新たに作成します.このローカルスコープでプライベート変数などを宣言してもいいです.外部のコンテキストでは、直接にこのローカルスコープ内の要素にアクセスできないです.上記の例では、内部の関数は外部のコンテキストで宣言された変数にアクセスすることができ、逆に実行できない.では、これはいったいどういう理由ですか?エンジン内部はどうなりますか?
    コンテキストスタックを実行
    ブラウザでは、javascriptエンジンの働き方はシングルスレッドです.つまり、ある時点で唯一のイベントだけがアクティブ処理され、他のイベントはキューに入れられ、処理されるのを待つ.以下の例図は、このようなスタックを説明する.
    JAvascriptコードファイルがブラウザにロードされた後、デフォルトで最初に入るのはグローバルの実行文脈であることはすでに知っています.グローバルコンテキストで関数の実行が呼び出されると、プログラムフローは呼び出された関数の中に入ります.このとき、エンジンは関数のために新しい実行コンテキストを作成し、コンテキストスタックの実行の上部に押し込みます.ブラウザは常にスタックの上部にある現在のコンテキストを実行し、実行が終了すると、スタックの上部からコンテキストがポップアップされ、その下のコンテキストに入ってコードを実行します.このように、スタック内のコンテキストは、グローバルなコンテキストに戻るまで、順次実行され、スタックをポップアップする.次の例を見てください.
        
            (function foo(i) {
                if (i === 3) {
                    return;
                }
                else {
                    foo(++i);
                }
            }(0));
        
    
    上記のfooが宣言された後、演算子によって強制的に直接運転されました.関数コードとは、自身を3回呼び出し、毎回ローカル変数iを1つ追加します.foo関数が自分で呼び出されるたびに、新しい実行コンテキストが作成されます.コンテキストが実行されるたびに、コンテキストがポップアップされてスタックに戻り、再びグローバルコンテキストに戻るまで前のコンテキストに戻ります.真の過程のイメージは次の図のようです.
    このことから、文脈という抽象的な概念を実行するには、以下の点に要約することができる.
  • 単スレッド
  • 同期実行
  • 唯一のグローバルコンテキスト
  • 関数の実行文脈の個数は制限されていません.
  • は、ある関数が呼び出されるたびに、呼び出し自体の関数であっても、新しい実行コンテキストを作成します.
  • コンテキストの作成プロセスを実行します.
    関数を呼び出すたびに、新しい実行コンテキストが作成されることが分かりました.しかし、javascriptエンジンの内部では、このコンテキストの作成プロセスは具体的に2つの段階に分けられています.
  • 確立段階(関数が呼び出されたときに発生しますが、関数内の具体的なコードが実行される前に)
  • 変数、関数、argmentsオブジェクト、パラメータ
  • を確立します.
  • 作用分域チェーン
  • を確立する.
  • は、thisの値
  • を決定する.
  • コードの実行段階:
  • 変数の割り当て、関数参照、他のコード
  • を実行します.
    実際には、実行文脈を対象としてもよく、その下には上記の3つの属性が含まれています.
        
              (executionContextObj = {
                variableObject: { /*     arguments  ,   ,             */ },
                scopeChain: { /* variableObject             variableObject */ },
                this: {}
              }
        
    
    確立段階とコード実行段階の詳細分析
    正確には、コンテキストオブジェクト(上述のexecution ContectObj)を実行することは、関数が呼び出されるときであるが、関数が本当に実行される前に作成される.関数が呼び出された時は、上記の2つの段階のうちの最初の段階である、確立段階です.この時点で、エンジンは関数のパラメータ、宣言の変数、および内部関数をチェックし、これらの情報に基づいてコンテキストオブジェクトを作成します.この段階では、variable Objectオブジェクト、作用ドメインチェーン、およびthisが指すオブジェクトが確定されます.
    上記の第一段階の具体的なプロセスは以下の通りである.
  • は、現在のコンテキストの呼び出し関数のコード
  • を見つける.
  • は、呼び出された関数体のコードを実行する前に、実行コンテキスト
  • の作成を開始する.
  • は第一段階に入る.
  • variable Objectオブジェクトを確立する:
  • は、argmentsオブジェクトを確立し、現在のコンテキストにおけるパラメータを確認し、オブジェクトの下の属性および属性値
  • を確立する.
  • は現在のコンテキストの関数声明をチェックします.関数宣言を見つけるごとに、variableObjectの下に関数名で属性を作成します.属性値はこの関数のメモリ内のアドレスを指す参照です.上記の関数名が既にvariableObjectの下に存在しているなら、対応する属性値は新しい参照によって上書きされます.
  • 初期化作用分域チェーン
  • は、コンテキストにおけるthisの指向性オブジェクト
  • を決定する.
  • コードの実行段階:関数体のコードを実行し、コードを1行ずつ実行し、variable Objectの変数属性に値を割り当てます.
  • 具体的なコード例を以下に示す.
        
            function foo(i) {
                var a = 'hello';
                var b = function privateB() {
            
                };
                function c() {
            
                }
            }
            
            foo(22);
        
    
    foo(22)を呼び出した時、確立段階は以下の通りです.
        
            fooExecutionContext = {
                variableObject: {
                    arguments: {
                        0: 22,
                        length: 1
                    },
                    i: 22,
                    c: pointer to function c()
                    a: undefined,
                    b: undefined
                },
                scopeChain: { ... },
                this: { ... }
            }
        
    
    これから分かるように、確立段階では、アーグメンツ、関数の声明、およびパラメータが特定の属性値を与えられているほか、他の変数属性はデフォルトでundefinedである.上記のセットアップ段階が終了すると、エンジンはコード実行段階に入ります.この段階が完了すると、上記の実行コンテキストオブジェクトは以下の通りです.
        
            fooExecutionContext = {
                variableObject: {
                    arguments: {
                        0: 22,
                        length: 1
                    },
                    i: 22,
                    c: pointer to function c()
                    a: 'hello',
                    b: pointer to function privateB()
                },
                scopeChain: { ... },
                this: { ... }
            }
        
    
    コード実行段階においてのみ、変数属性に特定の値が付与されることを示した.
    ローカル変数のスコープアップの缘由
    このような要約は、関数で宣言された変数および関数の役割領域が関数の上部に引き上げられ、言い換えれば関数に入ると、その中に宣言された変数および関数にアクセスすることができます.これは正しいですが、その理由は分かりますか?上記の説明を通じて、あなたも分かりました.でも、ここでもう一度分析してみます.次のコードを見てください.
        
            (function() {
                console.log(typeof foo); // function pointer
                console.log(typeof bar); // undefined
            
                var foo = 'hello',
                    bar = function() {
                        return 'world';
                    };
            
                function foo() {
                    return 'hello';
                }
            
            }());​
        
    
    上記のコードは匿名関数を定義し、()演算子の強制的な理解によって実行される.これを知っていると、実行文脈が作成されます.例ではすぐにfooやbar変数にアクセスできます.そして、typeof出力fooを通じて関数として参照します.barはundefinedです.
    なぜfoo変数を宣言する前にfooにアクセスできるのですか?
    文脈の確立段階では、まずargments、パラメータ、続いて関数の声明、最後に変数の声明を処理するからです.foo関数の声明が見つかったら、variaboleObjectの下にfoo属性を作成します.その値は指向関数の参照です.変数宣言を処理すると、var fooの声明がありますが、variable Objectはすでにfoo属性を持っていますので、直接スキップします.コード実行段階に入ると、foo属性にアクセスできます.既に存在し、関数参照です.
    なぜbarはundefinedですか?
    barは変数の声明なので、段階を作る時、与えられたデフォルトの値はundefinedです.コード実行段階で特定の値が与えられるので、typeof(bar)を呼び出したときに出力される値はundefinedです.
    はい、ここまでです.実行文脈は分かります.この実行文脈の概念はとても重要です.必ずよく分かります.