JavaScriptの実行文脈と実行スタックの実例解説


JavaScript-原理シリーズ
日常開発では、既存のプロジェクトを引き継ぐたびに、まず他の人が書いたコードを見に行くのが好きです。他の人がクールなコードを書いているのを見るたびに、私たちはいつも感慨深いです。こんなに優美で簡潔なコードを書いた兄弟は一体どうやって身につけましたか?
どうやってオーディと同じレベルに達することができますか?じゃ、余計なことを言わないで、今日のテーマに切り込みましょう。
一、実行コンテキスト
簡単に言えば、【実行コンテキスト】とは、JavaScriptコードが解析され実行される時の環境の抽象概念であり、JavaScriptで実行される任意のコードは、その実行コンテキストで実行されます。
JavaScriptコードを実行する時、コードを実行する必要があるたびに、実行コードはまず環境(ブラウザ、Nodeクライアント)に入ります。この場合、この環境のために実行コンテキストを作成します。コードを実行する前に、スコープを確定する、グローバル、局部変数オブジェクトを作成するなどの準備をします。
コンテキストの分類を実行
  • グローバル実行コンテキスト:
  • これはデフォルトの、基本的な実行文脈です。どの関数にもないコードは、グローバル実行コンテキストにあります。
    二つのことをしました。
  • は、全体のオブジェクトを作成し、ブラウザでは、この全体のオブジェクトはwindowオブジェクトです。
  • thisポインタは、このグローバルオブジェクトに向けられている。一つのプログラムにはグローバル実行コンテキストが存在するだけです。
  • 関数実行コンテキスト:
  • 関数を呼び出すたびに、新しい実行コンテキストが作成されます。各関数は自分の実行コンテキストを持っていますが、関数が呼び出されたときだけ作成されます。一つのプログラムには任意の数の関数が存在してコンテキストを実行できます。新しい実行コンテキストが作成されるたびに、特定の順序で一連のステップが実行され、特定のプロセスは本明細書の後で議論される。
  • Eval関数実行コンテキスト:
  • eval関数で実行されるコードも自身の実行コンテキストを得たが、Javascript開発者はeval関数をあまり使わないので、ここでは議論しない。
    コンテキストの数制限(スタックオーバーフロー)を実行します。
    実行コンテキストは、明確な数の制限はないが、スタック割り当てられた空間を超えると、スタックオーバーフローを引き起こす複数の存在があり得る。再帰的呼び出しによく見られますが、終了条件なしでデッドサイクルを引き起こすシーンがあります。
    以下は例示コードです。
    
    //       
    function foo() {
      foo();
    }
    foo();
    //   :Uncaught RangeError: Maximum call stack size exceeded
    Tips:
    JSは「シングルスレッド」です。毎回コードの一部だけを実行します。
    二、執行スタック
    JSの実行スタック、すなわち他のプログラミング言語でいう「呼び出しスタック」は、LIFO(後進先出)データ構造を持つスタックであり、コード実行時に作成されたすべての実行コンテキストを記憶するために使用される。
    JavaScriptエンジンが初めてあなたのスクリプトに出会った時、グローバルな実行コンテキストを作成し、現在の実行スタックに押し込みます。エンジンが関数呼出しに会うたびに、関数のために新しい実行コンテキストを作成し、スタックの上部に押し込む。
    エンジンは、スタックの一番上にあるこれらの実行コンテキストの関数を実行します。関数の実行が終了すると、コンテキストがスタックからポップアップされ、制御フローが現在のスタックの次のコンテキストに到達します。
    スタックデータ構造

    ここでは一部のコードを使って、実行スタックを理解しましょう。
    
    let a = 'Hello World!';
    
    function first() {
     console.log('Inside first function');
     second();
     console.log('Again inside first function');
    }
    
    function second() {
     console.log('Inside second function');
    }
    
    first();
    console.log('Inside Global Execution Context');
    下図は上のコードの実行スタックです。

    上記のコードがブラウザで読み込まれると、ブラウザのJavaScriptエンジンはグローバル実行コンテキストを作成し、現在の実行スタックに押し込む。関数が呼び出されたとき、JavaScriptエンジンはこの関数のために新しい実行コンテキストを作成し、現在の実行スタックの上部に押し込む。first()関数の内部からsecond()関数を呼び出すと、JavaScriptエンジンはsecond()関数で新しい実行コンテキストを作成し、現在の実行スタックの上部に押し込む。second()関数の実行が終了すると、その実行コンテキストは現在のスタックからイジェクトされ、制御フローは次の実行コンテキスト、すなわちfirst()関数の実行コンテキストに達する。first()が実行が完了すると、その実行コンテキストはスタックからポップアップされ、制御フローは大域的な実行コンテキストに到達する。すべてのコードが実行されると、JavaScriptエンジンは現在のスタックからグローバル実行コンテキストを削除します。
    The Creation Phite
    JavaScriptコードが実行される前に、実行コンテキストは作成段階を経ます。創建段階では三つのことがあります。
  • this値の決定は、我々がよく知っているThisバインディングである。
  • ワード環境コンポーネントを作成します。
  • は、変数環境コンポーネントを作成します。
  • したがって、実行文脈は概念的に以下のように表される。
    
    ExecutionContext = {
     ThisBinding = <this value>,
     LexicalEnvironment = { ... },
     VariableEnvironment = { ... },
    }
    Thisバインディング:
    グローバル実行コンテキストにおいて、thisの値はグローバルオブジェクトに向けられている。ブラウザでは、thisはWindowオブジェクトを参照する。
    関数実行コンテキストにおいて、thisの値は、関数がどのように呼び出されるかに依存する。参照オブジェクトによって呼び出されると、thisはそのオブジェクトに設定され、そうでないとthisの値はグローバルオブジェクトまたはundefinedに設定される。たとえば:
    
    let foo = {
     baz: function() {
     console.log(this);
     }
    }
    foo.baz();  // 'this'    'foo',    'baz'  
           //    'foo'   
    let bar = foo.baz;
    bar();    // 'this'      window   ,  
           //         
    ロケーション
    公式ES 6文書は品詞環境を
    語法環境は規範タイプで、ECMAScriptコードの語法ネスト構造に基づいて、識別子と具体的な変数と関数の関連を定義します。一つの語法環境は、環境レコーダと外部の語法環境を引用する可能性のある空の値から構成される。
    簡単に言えば、語法環境は、識別子・変数マッピングを持つ構造である(ここでの識別子は変数/関数の名前を指し、変数は実際のオブジェクトに対して「関数タイプのオブジェクトを含む」または元のデータの参照です。
    現在、語法環境の内部には、(1)環境レコーダと(2)一つの外部環境の参照という二つのコンポーネントがある。
  • 環境レコーダは、変数および関数宣言を格納する実際の位置である。
  • 外部環境の引用は、親レベルの語法環境(作用領域)にアクセスできることを意味する。
  • 語法環境には2つのタイプがあります。
  • グローバル環境(グローバル実行コンテキスト)は、外部環境参照がないロケーションである。グローバル環境の外部環境参照はnullです。これは、内部に構築されたObject/Aray/などの環境レコーダ内のプロトタイプ関数(windowオブジェクトなどのグローバルオブジェクトに関連する)と、ユーザーによって定義されたグローバル変数があり、thisの値はグローバルオブジェクトを指す。
  • は、関数環境において、環境レコーダに関数内部ユーザ定義の変数を格納する。また、参照される外部環境は、グローバル環境であってもよく、またはこの内部関数を含む外部関数であってもよい。
  • 環境レコーダにも二つのタイプがあります。
  • 宣言式環境レコーダは変数、関数、およびパラメータを記憶する。
  • オブジェクト環境レコーダは、大域コンテキストに現れる変数と関数の関係を定義するために使用される。
  • 簡単に言えば
  • は、グローバル環境において、環境レコーダは、対象環境レコーダである。
  • は、関数環境において、環境レコーダは、宣言式環境レコーダである。
  • 注意
    関数環境については、宣言型環境レコーダは、関数に伝達されるargumentsオブジェクト(このオブジェクトはインデックスとパラメータのマッピングを記憶する)と、関数に伝達されるパラメータのlengthをさらに含む。
    抽象的に言って、語法環境は疑似コードの中でこのように見えます。
    
    GlobalExectionContext = {
     LexicalEnvironment: {
      EnvironmentRecord: {
       Type: "Object",
       //         
      }
      outer: <null>
     }
    }
    
    FunctionExectionContext = {
     LexicalEnvironment: {
      EnvironmentRecord: {
       Type: "Declarative",
       //         
      }
      outer: <Global or outer function environment reference>
     }
    }
    変数の環境:
    これは同じように、環境レコーダが変数宣言文を保持し、実行コンテキストで作成したバインディング関係です。
    上記のように変数環境もワード環境であるため、上記で定義されているワード環境のすべての属性を持っています。
    ES 6では、ロケールコンポーネントおよび変数環境の違いの一つは、前者が関数宣言および変数(letおよびconst)を格納するために使用され、後者はvarの変数バインディングを保存するために使用されるだけである。
    私たちはサンプルコードを見て上の概念を理解します。
    
    let a = 20;const b = 30;var c;
    function multiply(e, f) { var g = 20; return e * f * g;}
    c = multiply(20, 30);
    実行コンテキストはこのように見えます。
    
    GlobalExectionContext = {
    
     ThisBinding: <Global Object>,
    
     LexicalEnvironment: {
      EnvironmentRecord: {
       Type: "Object",
       //         
       a: < uninitialized >,
       b: < uninitialized >,
       multiply: < func >
      }
      outer: <null>
     },
    
     VariableEnvironment: {
      EnvironmentRecord: {
       Type: "Object",
       //         
       c: undefined,
      }
      outer: <null>
     }
    }
    
    FunctionExectionContext = {
     ThisBinding: <Global Object>,
    
     LexicalEnvironment: {
      EnvironmentRecord: {
       Type: "Declarative",
       //         
       Arguments: {0: 20, 1: 30, length: 2},
      },
      outer: <GlobalLexicalEnvironment>
     },
    
    VariableEnvironment: {
      EnvironmentRecord: {
       Type: "Declarative",
       //         
       g: undefined
      },
      outer: <GlobalLexicalEnvironment>
     }
    }
    注意
    関数実行コンテキストは、呼び出し関数multiplyに遭遇した場合にのみ作成されます。letおよびconstによって定義された変数は、いかなる値にも関連していないことに気づいたかもしれないが、varによって定義された変数はundefinedに設定されている。
    これは、関数宣言が環境に完全に格納されているが、変数が最初にundefinedvarの場合)に設定されていたり、初期化されていない(letおよびconstの場合)ためである。
    これは、ステートメントの前にvarによって定義された変数にアクセスすることができる理由である(undefinedであるが、ステートメントの前にletおよびconstにアクセスする変数は、参照エラーを得ることができる。
    これは私たちが言っている変数宣言の向上です。
    実行フェーズ
    これは文章の中で一番簡単な部分です。この段階では、これらのすべての変数の割り当てが完了し、最後にコードが実行されます。
    注意
    実行段階では、JavaScriptエンジンがソースコードで宣言できない場合は、実際の位置にlet変数の値が見つかります。undefinedに値が与えられます。
    結論
    私たちはJavaScriptプログラムの内部でどのように実行されるかを議論しました。優れたJavaScript開発者になるためには、これらのすべての概念を習得する必要はありませんが、上の概念を正しく理解することができれば、より簡単に、他の概念をより深く理解することができます。
    参考記事:
    https://juejin.cn/post/6844903682283143181
    https://www.jianshu.com/p/6f8556b10379
    https://juejin.cn/post/6844903704466833421
    ここでは、JavaScriptの実行文脈と実行スタックの実例について解説した文章を紹介します。JavaScriptの実行文脈と実行スタックの内容については、以前の文章を検索してください。