JavaScript での実行コンテキストと実行スタックの例の説明

JavaScript での実行コンテキストと実行スタックの例の説明

JavaScript - 原則シリーズ

日常の開発では、既存のプロジェクトを引き継ぐときは常に、まず他の人が書いたコードを確認するようにしています。誰かがかっこいいコードを書いているのを見ると、いつもため息をついてしまいます。こんなに美しく簡潔なコードを書く才能はどうやって培ったのですか?

どうすれば大物と同じレベルに到達できるでしょうか?さて、これ以上前置きはせずに、今日の話題に入りましょう。

1. 実行コンテキスト

簡単に言うと、[実行コンテキスト] とは、JavaScript コードが解析され実行される環境の抽象的な概念です。JavaScript で実行されるすべてのコードは、その実行コンテキスト内で実行されます。

JavaScript コードを実行する場合、コードを実行する必要があるときはいつでも、コードは最初に環境 (ブラウザー、Node クライアント) に入り、次にその環境の実行コンテキストが作成されます。これにより、スコープの決定、グローバルおよびローカル変数オブジェクトの作成など、コードを実行する前にいくつかの準備が行われます。

実行コンテキストの分類

  • グローバル実行コンテキスト:

これはデフォルトの最も基本的な実行コンテキストです。どの関数にも含まれていないコードは、グローバル実行コンテキストにあります。

それは次の 2 つのことを行います:

  • グローバル オブジェクトを作成します。ブラウザーでは、このグローバル オブジェクトはウィンドウ オブジェクトです。

thisポインターをグローバル オブジェクトを指すように設定します。プログラム内に存在できるグローバル実行コンテキストは 1 つだけです。

  • 関数実行コンテキスト:

関数が呼び出されるたびに、その関数の新しい実行コンテキストが作成されます。各関数には独自の実行コンテキストがありますが、関数が呼び出されたときにのみ作成されます。プログラムには任意の数の関数実行コンテキストが存在できます。新しい実行コンテキストが作成されるたびに、一連のステップが特定の順序で実行されます。これについては、この記事の後半で説明します。

  • eval関数の実行コンテキスト:

eval関数で実行されるコードも独自の実行コンテキストを取得しますが、JavaScript 開発者は eval 関数をあまり使用しないため、ここでは説明しません。

実行コンテキスト数の制限(スタックオーバーフロー)

実行コンテキストは複数存在できます。明確な数制限はありませんが、スタックで割り当てられた領域を超えるとスタックオーバーフローが発生します。これは、終了条件がなく、無限ループが発生する再帰呼び出しでよく発生します。

サンプルコードは次のとおりです。

// 自分自身を再帰的に呼び出す function foo() {
  関数 foo();
}
関数 foo();
// エラー: 捕捉されない RangeError: 最大呼び出しスタック サイズを超えました

ヒント:

JSは「シングルスレッド」であり、一度に1つのコードのみを実行します。

2. 実行スタック

JS の実行スタックは、他のプログラミング言語では「コール スタック」とも呼ばれ、コードの実行時に作成されるすべての実行コンテキストを格納するために使用される LIFO (後入れ先出し) データ構造を持つスタックです。

JavaScript エンジンが最初にスクリプトに遭遇すると、グローバル実行コンテキストが作成され、それが現在の実行スタックにプッシュされます。エンジンは関数呼び出しに遭遇するたびに、その関数の新しい実行コンテキストを作成し、それをスタックの一番上にプッシュします。

エンジンは、実行コンテキストがスタックの最上部にある関数を実行します。関数の実行が終了すると、実行コンテキストがスタックからポップされ、制御フローは現在のスタック内の次のコンテキストに到達します。

スタックデータ構造

では、コードを使って実行スタックを理解してみましょう。

a = 'Hello World!' とします。

関数first(){
 console.log('最初の関数内');
 2番目();
 console.log('再び最初の関数内');
}

関数 second() {
 console.log('2番目の関数内');
}

初め();
console.log('グローバル実行コンテキスト内');

次の図は、上記のコードの実行スタックです。

上記のコードがブラウザに読み込まれると、ブラウザの JavaScript エンジンはグローバル実行コンテキストを作成し、それを現在の実行スタックにプッシュします。関数呼び出しが発生すると、JavaScript エンジンは関数の新しい実行コンテキストを作成し、それを現在の実行スタックの一番上にプッシュします。

2 番目のfirst() second()関数内から呼び出されると、JavaScript エンジンはsecond()関数の新しい実行コンテキストを作成し、それを現在の実行スタックの一番上にプッシュします。 second()関数の実行が終了すると、その実行コンテキストは現在のスタックからポップされ、制御フローは次の実行コンテキスト、つまりfirst()関数の実行コンテキストに到達します。

first()実行が終了すると、その実行コンテキストはスタックからポップされ、制御はグローバル実行コンテキストに流れます。すべてのコードが実行されると、JavaScript エンジンは現在のスタックからグローバル実行コンテキストを削除します。

創造段階

JavaScript コードが実行される前に、実行コンテキストは作成フェーズを経ます。作成フェーズでは次の 3 つのことが起こります。

  1. この値の決定は、This バインディングと呼ばれます。
  2. LexicalEnvironment コンポーネントを作成します。
  3. 可変環境コンポーネントを作成します。

したがって、実行コンテキストは概念的に次のように表されます。

実行コンテキスト = {
 ThisBinding = <この値>,
 レキシカル環境 = { ... },
 変数環境 = { ... },
}

このバインディング:

グローバル実行コンテキストでは、 thisの値はグローバル オブジェクトを参照します。 (ブラウザでは、 this Window オブジェクトを指します)。

関数実行のコンテキストでは、 thisの値は関数の呼び出し方法によって異なります。参照オブジェクトで呼び出された場合、 thisそのオブジェクトに設定され、それ以外の場合は、 thisの値はグローバル オブジェクトまたはundefined (厳密モードの場合) に設定されます。例えば:

foo = {
 baz: 関数() {
 console.log(これを);
 }
}
foo.baz(); // 'this' は 'foo' を参照します。なぜなら 'baz' はオブジェクト 'foo' で呼び出されるからです。let bar = foo.baz;
bar(); // 'this' は参照オブジェクトが指定されていないため、グローバルウィンドウオブジェクトを参照します

語彙環境

ES6の公式ドキュメントでは、語彙環境を次のように定義しています。

レキシカル環境は、ECMAScript コードのレキシカル ネスト構造に基づいて、識別子と特定の変数および関数との関連付けを定義する仕様タイプです。語彙環境は、環境レコーダーと、外部の語彙環境を参照する空の値(場合によっては)で構成されます。


簡単に言えば、語彙環境は識別子と変数のマッピングを保持する構造です。 (ここで、識別子は変数/関数の名前を参照し、変数は実際のオブジェクト (関数型オブジェクトを含む) またはプリミティブ データへの参照です)。

現在、LexicalEnvironment 内には、(1) EnvironmentRecordant と (2) outerEnvironment への参照という 2 つのコンポーネントがあります。

  1. 環境レコーダーは、変数と関数の宣言が実際に保存される場所です。
  2. 外部環境への参照は、親の語彙環境 (スコープ) にアクセスできることを意味します。

語彙環境には 2 つの種類があります。

  • グローバル環境 (グローバル実行コンテキスト内) は、外部環境が参照されない語彙環境です。グローバル環境の外部環境参照が null です。組み込みのオブジェクト/配列など、環境レコーダー内のプロトタイプ関数 (ウィンドウ オブジェクトなどのグローバル オブジェクトに関連付けられているもの)、およびユーザー定義のグローバル変数があり、 this値はグローバル オブジェクトを参照します。
  • 関数環境では、関数内のユーザー定義変数は環境レコーダーに保存されます。参照される外部環境は、グローバル環境、またはこの内部関数を含む任意の外部関数である可能性があります。

環境ロガーにも 2 つの種類があります (上記のとおり)。

  1. 宣言型環境レコーダーは、変数、関数、およびパラメータを保存します。
  2. オブジェクト環境レコーダーは、グローバル コンテキストに表示される変数と関数間の関係を定義するために使用されます。

要するに

  • グローバル環境では、環境ロガーはオブジェクト環境ロガーです。
  • 関数環境では、環境ロガーは宣言型の環境ロガーです。

知らせ

関数環境の場合、宣言型環境レコーダーには、関数に渡されるargumentsオブジェクト (このオブジェクトにはインデックスとパラメータのマップが格納されます) と、関数に渡される引数の長さも含まれます。

抽象的には、語彙環境は擬似コードで次のようになります。

グローバル実行コンテキスト = {
 語彙環境:
  環境レコード: {
   タイプ:「オブジェクト」、
   // バインド識別子をここに記述します }
  外側: <null>
 }
}

関数実行コンテキスト = {
 語彙環境:
  環境レコード: {
   タイプ:「宣言型」、
   // バインド識別子をここに記述します }
  外側: <グローバルまたは外部関数環境参照>
 }
}

可変環境:

これは、実行コンテキスト内の変数宣言ステートメントによって作成されたバインディングを環境レコーダーが保持するレキシカル環境でもあります。

前述のように、変数環境も語彙環境であるため、上で定義した語彙環境のすべてのプロパティを持ちます。

ES6 では、LexicalEnvironment コンポーネントと VariableEnvironment の違いの 1 つは、前者は関数宣言と変数 ( letconst ) バインディングを格納するために使用されるのに対し、後者はvar変数バインディングを格納するためにのみ使用されることです。

上記の概念を理解するために、いくつかのサンプル コードを見てみましょう。

a = 20;const b = 30;var c; とします。
関数 multiply(e, f) { var g = 20; return e * f * g;}
c = 乗算(20, 30);

実行コンテキストは次のようになります。

グローバル実行コンテキスト = {

 ThisBinding: <グローバル オブジェクト>、

 語彙環境:
  環境レコード: {
   タイプ:「オブジェクト」、
   // ここで識別子 a をバインドします: < 初期化されていません >、
   b: < 初期化されていません >、
   乗算: < 関数 >
  }
  外側: <null>
 },

 変数環境: {
  環境レコード: {
   タイプ:「オブジェクト」、
   // ここで識別子 c をバインドします: undefined、
  }
  外側: <null>
 }
}

関数実行コンテキスト = {
 ThisBinding: <グローバル オブジェクト>、

 語彙環境:
  環境レコード: {
   タイプ:「宣言型」、
   // ここで識別子をバインドします 引数: {0: 20, 1: 30, 長さ: 2},
  },
  外側: <GlobalLexicalEnvironment>
 },

変数環境: {
  環境レコード: {
   タイプ:「宣言型」、
   // ここで識別子 g をバインド: undefined
  },
  外側: <GlobalLexicalEnvironment>
 }
}

知らせ

関数実行コンテキストは、関数multiplyが呼び出されたときにのみ作成されます。

letconstで定義された変数には値が関連付けられていませんが、 varで定義された変数はundefinedに設定されていることに気付いたかもしれません。

これは、作成フェーズでエンジンがコードを検査して変数と関数の宣言を見つけ、関数の宣言は環境に完全に保存されるのに対し、変数は最初はundefined ( varの場合) または unitialized ( 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 の実行コンテキストと実行スタックの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScript実行メカニズムの詳細な説明
  • JavaScriptの実行メカニズムを徹底的に理解する
  • JavaScript の実行コンテキストとコールスタックの詳細な説明
  • JS 非同期実行の原則とコールバックの詳細
  • JavaScript実行モデルの詳細な説明
  • Javascript 実行コンテキスト順序の詳細な説明
  • JavaScript モジュール エグゼキュータを手動で実装する方法
  • Javascript 非同期プロセス制御シリアル実行の詳細な説明
  • いくつかの面接の質問を使ってJavaScriptの実行メカニズムを調べる

<<:  Dockerコンテナの紹介

>>:  MySQL 5.7.17 最新インストールチュートリアル(画像とテキスト付き)

推薦する

jQueryはアコーディオンの小さなケースを実装します

この記事では、アコーディオンを実装するためのjQueryの具体的なコードを参考までに紹介します。具体...

MySQL PXC は IST 送信のみで新しいノードを構築します (推奨)

需要シナリオ: 既存の PXC 環境には大量のデータがあります。新しく購入したサーバーをこのクラスタ...

Linux で crond ツールを使用してスケジュールされたタスクを作成する方法

序文Crond は Linux のスケジュール実行ツール (Windows のスケジュールされたタス...

Docker5フル機能の港湾倉庫構築プロセス

Harbor は、Docker イメージを保存および配布するためのエンタープライズ レベルのレジスト...

Dockerコンテナデータをコピーしてバックアップする方法の詳細な説明

ここでは、Jenkins コンテナを例に 3 つの方法を紹介します。方法1コンテナをイメージにパッケ...

高品質なJavaScriptコードの書き方

目次1. 読みやすいコード1. 統一コード形式2. マジックナンバーを削除する3. 単一機能原則2....

docker を使用してシンプルな C/C++ プログラムをデプロイする方法

1. まずhello-world.cppファイルを作成しますプログラムコードは次のとおりです。 #i...

LinuxサーバのSSHクラッキング防止方法(推奨)

1. Linuxサーバーは、/etc/hosts.denyを設定して、相手のIPがSSH経由でサー...

Baotaパネルを再起動すると、「-ModuleNotFoundError: No module named 'geventwebsocket'」というメッセージが表示されます。

背景:サーバーがFlaskプロジェクトをデプロイし、python3をインストールしたため、再起動時に...

Dockerはプライベートライブラリイメージを完全に削除します

まず、インターネット上の一般的な慣行を見てみましょうデフォルトでは、プライベート ライブラリはイメー...

VMware 仮想マシン ubuntu18.04 インストール チュートリアル

インストール手順1. 仮想マシンを作成する 2. [カスタム(詳細)]を選択し、[次へ]をクリックし...

MySQLテクノロジーにおけるInnoDBロックの詳細な説明

目次序文1. ロックとは何ですか? 2. InnoDBストレージエンジンのロック2.1 ロックの種類...

Webpack4プラグインの実装原理についての簡単な説明

目次序文知る練習すれば完璧になる序文wabpack では、ローダーの他にプラグインがコア機能です。プ...

Vueは動的に生成されたコンポーネントをドラッグアンドドロップする要件を実装します

目次製品要件アイデア問題ライブラリ選択をドラッグコンポーネントを生成する方法コンポーネントを生成する...

MySQL 5.7 でルートパスワードを変更する方法

MySQL 5.7 以降では、多くのセキュリティ更新が追加されました。旧バージョンのユーザーは慣れて...