不求谌解

不求谌解

💻 Web Dev / Creative 💗 ⚽ 🎧 🏓
twitter
github
jike
email

再探js実行メカニズム

image

はじめに#

フロントエンドに入ってからもう 2 年以上になります。以前、私はフロントエンドの領域の変化と変わらないものを理解する必要があるということを記事で書きました。今も 2 年前と同じく、さまざまなフレームワークが次々と登場し、目まぐるしくなっています。多くの場合、「これはまだ学ぶ必要がありますか?」と自問自答してしまいます。これは私に焦りを感じさせることがよくあります。また、新しいものに直面すると、私は手探り状態になることがよくあります。根本的には、基礎知識が不十分であり、内部力が足りないからです。これが私がこの記事を書く目的です - 内部力を高め、『変化と変わらない』をより良く理解するためです。

はじめましょう#

このコードブロックから探索の旅を始めましょう

console.log('はじめました')

// setTimeout1
setTimeout(function () {
  console.log('timeout1')
  // promise1
  new Promise(function (resolve) {
    console.log('timeout1_promise')
    resolve()
  }).then(function () {
    console.log('timeout1_then')
  })
}, 2000)

for (var i = 1; i <= 5; i++) {
  // setTimeout2
  setTimeout(function () {
    console.log(i)
  }, i * 1000)
  console.log(i)
}

// promise2
new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('then1')
})

// setTimeout3
setTimeout(function () {
  console.log('timeout2')
  // promise3
  new Promise(function (resolve) {
    console.log('timeout2_promise')
    resolve()
  }).then(function () {
    console.log('timeout2_then')
  })
}, 1000)

// promise4
new Promise(function (resolve) {
  console.log('promise2')
  resolve()
}).then(function () {
  console.log('then2')
})

始める前に、問題を考えるのが好きな方々には 2 つの問題があるかもしれません。なぜ JavaScript はシングルスレッド言語と設計されたのか、そしてなぜ同期タスクと非同期タスクが区別されるのか。これらの問題については、ここでは詳しく説明しません。興味のある方は、阮一峰先生のこの記事をご覧ください。
さて、JavaScript の実行方法をざっくりと見てみましょう。

イベントループ

どのタスクがマクロタスクであり、どのタスクがマイクロタスクなのかを具体的に見てみましょう。

マクロタスク(macro-task)

  • script (JavaScript の全体的なコード)
  • setTimeout()
  • setInterval()
  • setImmediate()
  • I/O
  • UI のレンダリング

マイクロタスク(micro-task)

  • promise
  • async/await (同じく)
  • process.nextTick
  • MutationObserver

準備がほぼ整いましたので、上記のコードを実行してみましょう。

  • まず、全体のスクリプトがメインスレッドに入り、console.log()に遭遇し、すぐに『はじめました』と出力されます。
  • 次に、setTimeoutに遭遇し、2 秒後にコールバック関数 function () がマクロタスクイベントキューにディスパッチされます(ここで 2 秒後に実行されるわけではありません)。これを setTimeout1 とマークします。
  • forに遭遇し、直接実行され、同様にsetTimeoutのコールバック関数がマクロタスクイベントキューにディスパッチされます(ここではクロージャの知識が関係しています)、これを setTimeout2 とマークし、次にconsole.logを実行して『1、2、3、4、5』と出力します。
  • promiseに遭遇し、new Promiseが直ちに実行され、『promise1』が出力されます。thenはマイクロタスクイベントキューにディスパッチされ、then1 とマークされます。
  • 次に、setTimeoutに再び遭遇し、1 秒後にコールバック関数 function () がマクロタスクイベントキューにディスパッチされ、これを setTimeout3 とマークします。
  • またpromiseに遭遇し、同様に実行され、『promise2』が出力され、thenがマイクロタスクイベントキューにディスパッチされ、then2 とマークされます。

最初のイベントループのマクロタスクがすべて実行されました。イベントキューのタスクは以下の表のようになります。

マクロタスクマイクロタスク
setTimeout1 (2 秒後)then1
setTimeout2 (1 秒後)then2
setTimeout3 (1 秒後)

上記のフローチャートに従って、マクロタスクが実行された後、JavaScript エンジンの監視プロセスは実行可能なマイクロタスクがあるかどうかをチェックします。この時、マイクロタスクイベントキューにディスパッチされたthenが実行されます。順番に『then1』、『then2』と出力されます。

最初のイベントループが完了しました。

さて、第 2 のイベントループが始まります(1 秒後)。

  • setTimeout2に遭遇し、『6』を出力し、実行可能なマイクロタスクはありません。新しいマクロタスクを実行します。
  • setTimeout3に遭遇し、『timeout2』を出力し、new promiseが直ちに実行され、『timeout2_promise』が出力されます。thenはマイクロタスクイベントキューにディスパッチされ、then3 とマークされます。

第 2 のイベントループのマクロタスクがすべて実行されました。イベントキューのタスクは以下の表のようになります。

マクロタスクマイクロタスク
setTimeout1 (1 秒後)then3
setTimeout2 (1 秒後)

同様に、マクロタスクが実行された後、このイテレーションのマイクロタスク then3 が実行されます。

第 2 のイベントループが完了しました。

  • setTimeout1に遭遇し、console.logを実行し、『timeout1』を出力します。new promiseが直ちに実行され、『timeout1_promise』が出力され、thenはマイクロタスクイベントキューにディスパッチされ、then4 とマークされます。
  • 第 3 のイベントループのマクロタスクが実行され、このイテレーションのマイクロタスクthenが実行され、『timeout1_then』が出力されます。

第 3 のイベントループが完了しました。

  • setTimeout2に遭遇し、『6』を出力します。実行可能なマイクロタスクはありません。新しいマクロタスクを実行します。

以上で、コード全体の実行が終了しました。

要約すると、マクロタスクが実行され、そのマクロタスクが生成したマイクロタスクが実行されます。マイクロタスクの実行中に新しいマイクロタスクが生成された場合は、引き続きマイクロタスクが実行されます。マイクロタスクの実行が完了したら、マクロタスクに戻り、次のイテレーションを開始します。

async#

node#

参考資料#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。