こんにちは!Web開発の世界へようこそ。フロントエンドのアーキテクトをしている者です。
日頃、HTMLやCSS、そしてJavaScriptを書いていると、「あれ?なんでこのコードを書いたらいきなり画面が固まったんだろう?」「`setTimeout` とか `Promise` って、どっちが先に動くんだっけ?」と頭を悩ませた経験、ありませんか?
大丈夫ですよ、最初はみんなそこで迷子になります。目に見えないブラウザの裏側の動きって、まるでブラックボックスのようで不安になりますよね。でも、安心してください。今日からそのブラックボックスのフタを開けて、中身を一緒にのぞいてみましょう。
今回は、Webブラウザの心臓部である「レンダリングエンジンとイベントループの統合」について、難しい専門用語をできるだけ取っ払って、身近なたとえ話を交えながら優しく紐解いていきますね。
—
ブラウザの中には「超有能だけど不器用な職人」がいる
まず、Webブラウザのタブの中を一つの「工房」だと思って想像してみてください。この工房には、「レンダリング職人(画面を描く係)」と「JavaScript実行係(指示をこなす係)」という、2人の主要なキャラクターがいます。
そして、ここが一番大事なポイントなのですが、この工房の作業台は「1つ」しかありません。
つまり、職人が画面を描いているときは、JavaScriptのコードは実行できません。逆に、JavaScriptがゴリゴリと計算をしているときは、画面の描き換えは一時ストップします。
「えっ、じゃあどうやってアニメーションとかスムーズに動かしてるの?」って思いますよね。
そこで登場するのが、仕事を整理して順番にさばく「イベントループ(回転ずしコンベアのような仕組み)」なんです。
—
お仕事が並ぶ「2つのコンベアベルト」
JavaScriptの世界には、仕事を待たせておく「待ち行列(キュー)」が2つ存在します。これが、今回の主役である「タスクキュー」と「マイクロタスクキュー」です。
お買い物のレジに例えてみましょう。
1. マイクロタスクキュー(VIP専用の優先レーン)
- 例え:お会計の直前に「あ、レジ袋1枚追加で!」と思い出してサッと伝えるような、今やっている仕事のすぐ後、次の大きなお仕事に行く前に絶対に割り込ませたい用事です。
- 代表選手:`Promise` の `.then()` や `async/await` など。
2. タスクキュー(一般のお客さんの通常レーン)
- 例え:「クリックされたよ」「タイマーの時間が来たよ」という、まとまった大きさのイベント単位の用事です。
- 代表選手:`setTimeout`、`setInterval`、各種DOMイベント(`click` など)。
ブラウザは、次のようなルールでこのコンベアから仕事をひたすら処理しています。
> 【ブラウザの基本姿勢】
> 1. 通常レーン(タスクキュー)から、一番古い仕事を1つ取ってきて実行する。
> 2. その仕事の最中や直後に、VIPレーン(マイクロタスクキュー)に仕事がたまっていたら、コンベアが空っぽになるまでVIPの仕事をすべて片付ける!
> 3. 両方の仕事が一段落したら、いよいよ「画面を綺麗に描き直すフェーズ(レンダリング更新)」に入る。
—
画面が描き換わるタイミングを知ろう
さて、JavaScriptがせっせと仕事を片付け、VIPレーンも綺麗に空っぽになると、ブラウザは待ちに待った「レンダリング更新フェーズ(画面の再描画)」に入ります。
ここは、職人がキャンバスに絵の具を塗って、ピカピカの画面を私たちに見せてくれる大切な時間です。大体1秒間に60回(約16.6ミリ秒に1回)のペースで行われます。
もし、この「画面を描く時間」に、JavaScript側が「重たい計算ループ(無限ループや重い配列処理など)」を始めてしまうとどうなるでしょう?
職人は作業台を占領されてしまい、画面を新しく描き直すことができなくなります。これが、Webサイトが「カクカクする」「フリーズする」と言われる現象の正体です。
—
実際にコードを書いて、動きを確認してみましょう
百聞は一見にしかず。エディタを開いて、以下のコードをブラウザのコンソール(F12キーなどを押すと出てくる画面です)にペーストしてみてください。コンソールに表示される「順番」をよーく観察してみましょう。
// 【実習用コード】タスクとマイクロタスク、そして描画の順番を体感しよう
console.log(‘1. メインの同期処理:一番最初に走ります’);
// タスクキューに入る代表 (setTimeout)
setTimeout(() => {
console.log(‘4. タスクキュー(通常レーン):時間差でやってきたよ’);
}, 0);
// マイクロタスクキューに入る代表 (Promise)
Promise.resolve().then(() => {
console.log(‘3. マイクロタスクキュー(VIPレーン):同期処理の直後に割り込み!’);
});
console.log(‘2. メインの同期処理:最後まで走り抜けました’);
コンソールに出る結果:
1. メインの同期処理:一番最初に走ります
2. メインの同期処理:最後まで走り抜けました
3. マイクロタスクキュー(VIPレーン):同期処理の直後に割り込み!
4. タスクキュー(通常レーン):時間差でやってきたよ
どうですか? `setTimeout` に `0秒` と指定したのに、結果は `Promise` の方が先に出ましたよね。
これは、`setTimeout` が通常レーン(タスクキュー)行きなのに対し、`Promise` はVIPレーン(マイクロタスクキュー)行きだからです。ブラウザは「まずVIPを全員さばいてから、次の通常レーンへ行く」という鉄の掟を持っているため、この順番になるのです。
—
つまずきやすいポイントと、優しく温かいアドバイス
初心者の頃、私も「なんでタイマーで時間を `0` にしたのに、すぐに実行されないんだろう?」と大混乱しました。
でも、こう考えてみてください。
「JavaScriptは、目の前の仕事を一生懸命こなす真面目な職人さん。コンベア(イベントループ)から流れてくる順番に従って、一歩一歩確実に進んでいるだけなんだな」と。
もし、あなたが作ったWebサイトやアプリで「ボタンを押したのにアニメーションの反応が遅い」「入力がカクつく」という壁にぶぶつかったときは、こう疑ってみてください。
- 「もしかして、マイクロタスクや重たい処理でVIPレーンが大渋滞を起こして、ブラウザが画面を描く暇を失っているんじゃないか?」
- 「重たい計算は、別の人(Web Workerなど)にお願いできないか?」
一つひとつの仕組みはシンプルです。焦らず、ブラウザの気持ち(職人さんの気持ち)になってコードを眺めてあげると、エラーやパフォーマンスの悩みも、きっと楽しく解決できるようになりますよ。
今日の学びが、あなたの快適なフロントエンドライフの小さな灯りになればとても嬉しいです。それでは、また次の冒険でお会いしましょう!

コメント