不求谌解

不求谌解

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

再探js実行メカニズム

前書き#

フロントエンドに入ってから 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 はシングルスレッド言語として設計されているのか、なぜ同期タスクと非同期タスクの区別があるのか。この 2 つの質問については、ここでは詳しく述べません。興味のある方は阮先生のこの記事をご覧ください。
さて、js が大まかにどのように実行されるか見てみましょう。

Event Loop

一体どれがマクロタスクで、どれがマイクロタスクなのでしょうか。大まかにこのように区別します。

マクロタスク(macro-task)

  • script(全体の JavaScript コード)
  • setTimeout()
  • setInterval()
  • setImmediate()
  • I/O
  • UI render

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

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

予備知識はほぼ整いました。では、上記のコードを実行しましょう。

  • まず全体の script がメインスレッドに入り、console.log()に出会い、すぐに『始まりました』と出力します。
  • 次にsetTimeoutに出会い、2 秒後にコールバック関数 function () がマクロタスク Event Queue に分配されます(ここで注意すべきは、2 秒後に実行されるのではなく、分配されるということです)。ここでは setTimeout1 とマークします。
  • forに出会い、直接実行します。同様に、setTimeout内のコールバック関数がマクロタスク Event Queue に分配されます(ここではクロージャの知識が関係します)、setTimeout2 とマークし、次にconsole.logを実行し、『1、2、3、4、5』と出力します。
  • promiseに出会い、new Promiseが直接実行され、『promise1』と出力します。thenがマイクロタスク Event Queue に分配され、then1 とマークします。
  • 次にもう一つのsetTimeoutに出会い、1 秒後にコールバック関数 function () がマクロタスク Event Queue に分配され、setTimeout3 とマークします。
  • 再びpromiseに出会い、同様に『promise2』と出力し、thenがマイクロタスク Event Queue に分配され、then2 とマークします。

第一ラウンドのイベントループのマクロタスクはすでに実行が完了しました。Event Queue 内のタスクは以下の表の通りです。

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

上記のフローチャートに基づき、マクロタスクが完了した後、js エンジンの監視プロセスがこの時点で実行可能なマイクロタスクがあるかどうかを確認します。この時、マイクロタスク Event Queue に分配されたthenが実行されます。順次『then1』、『then2』と出力されます。

第一ラウンドのイベントループはすべて実行完了です。

さて、次に第二ラウンドのイベントループが始まります(1 秒後)。

  • setTimeout2に出会い、『6』と出力します。実行可能なマイクロタスクはありません。新しいマクロタスクを実行します。
  • setTimeout3に出会い、『timeout2』と出力し、new promiseが即座に実行され、『timeout2_promise』と出力します。thenがマイクロタスク Event Queue に分配され、then3 とマークします。第二ラウンドのイベントループのマクロタスクは実行完了しました。Event Queue 内のタスクは以下の表の通りです。
マクロタスクマイクロタスク
setTimeout1 (1 秒後)then3
setTimeout2 (1 秒後)

同様に、マクロタスクが実行完了した後、このラウンドのマイクロタスク then3 を実行します。

第二ラウンドのイベントループはすべて実行完了です。

  • setTimeout1に出会い、console.logを実行し、『timeout1』と出力します。new promiseが即座に実行され、『timeout1_promise』と出力し、thenがマイクロタスク Event Queue に分配され、then4 とマークします。
  • 第三ラウンドのイベントループのマクロタスクが実行完了し、このラウンドのマイクロタスクthenを実行し、『timeout1_then』と出力します。

第三ラウンドのイベントループは実行完了です。

  • setTimeout2の中で、4 つのマクロタスクが順次生成され、1 秒ごとに 6 を出力します。

これで、全てのコードが実行完了しました。

まとめ:マクロタスクが実行完了したら、そのマクロタスクによって生成されたマイクロタスクを実行します。もしマイクロタスクの実行中に新しいマイクロタスクが生成された場合、マイクロタスクを続けて実行します。マイクロタスクが完了したら、マクロタスクに戻り、次のラウンドを開始します。

async#

node#

参考資料#

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