やあ。今日もブラウザのメインスレッドと格闘しているか?
「なぜか画面がカクつく」「インタラクションが重い」——フロントエンドエンジニアが避けて通れないこの泥沼。その正体は、ブラウザの心臓部である「メインスレッド」の交通整理に失敗していることがほとんどだ。
今日は、ブラウザが裏側でどうタスクを捌いているのか、その「現場のリアル」を解剖していこう。教科書的な話は適当に読み飛ばして、俺たちが明日からコードを書く時に意識すべき「脳内OS」をインストールするつもりで読んでくれ。
—
1. メインスレッドは「孤独な料理人」だ
ブラウザのメインスレッドを一つの厨房だと想像してほしい。そこで働くのはたった一人のシェフだ。彼がやっていることは山積みだ。
- HTMLのパースとDOM構築
- CSSOMの構築とスタイルの計算
- レイアウト(配置の計算)とペイント(描画)
- JavaScriptの実行
- ユーザーイベント(クリックやスクロール)の処理
これら全てを、あの孤独なシェフが「タスクキュー」から一つずつ取り出してこなしている。JavaScriptで重い処理を走らせるということは、シェフに「この100万回ループが終わるまで、客(ユーザー)の注文には一切答えるな」と命令しているのと同じなんだ。これが「メインスレッドのブロック」の正体だ。
—
2. タスク管理の裏側:マクロとマイクロのせめぎ合い
ブラウザのタスク管理は、単純なFIFO(先入れ先出し)じゃない。ここには「階層」がある。
1. マクロタスク (Task Queue): `setTimeout`, `setInterval`, DOMイベント、レンダリング処理など。
2. マイクロタスク (Microtask Queue): `Promise.then`, `queueMicrotask`, `MutationObserver`など。
重要なルールはこれだ:
「一つのマクロタスクが終わるたびに、マイクロタスクキューが空になるまで全てを処理する」。
もし、JavaScriptで無限にマイクロタスクを生成したらどうなるか? メインスレッドは永久にマイクロタスクを処理し続け、画面の描画(レンダリング)に一生戻ってこれない。これが「ブラウザが固まる」という現象の、技術的に最も美しい(そして残酷な)理由だ。
—
3. 実践:重い処理を「分割」してメインスレッドを解放する
現場でよくあるミスは、大量のデータを一気に処理しようとすることだ。例えば、1万件のアイテムをレンダリングする時、`forEach`で一気にDOMを叩けば、メインスレッドは完全に沈黙する。
ここで、`requestIdleCallback`や`setTimeout`を使った「タスクの分割(Time Slicing)」というテクニックが活きてくる。
サンプルコード:メインスレッドを殺さないリスト生成
/
- 大量のデータをメインスレッドをブロックせずに処理する関数
- @param {Array} items – 処理対象のデータリスト
- @param {Function} processFn – 個別の要素に対する処理
/
function processLargeData(items, processFn) {
let index = 0;
function chunkProcessor(deadline) {
// ブラウザが暇な時間(deadline.timeRemaining)がある間だけ処理を続ける
while (index < items.length && deadline.timeRemaining() > 0) {
processFn(items[index]);
index++;
}
if (index < items.length) {
// まだ残っていたら、次のフレーム(ブラウザの空き時間)を待つ
requestIdleCallback(chunkProcessor);
} else {
console.log("処理完了!メインスレッドは一度も死んでいないはずだ。");
}
}
// 処理開始
requestIdleCallback(chunkProcessor);
}
// 使い方
const data = Array.from({ length: 10000 }, (_, i) => i);
processLargeData(data, (item) => {
// ここでDOM操作や計算を行う
const el = document.createElement(‘div’);
el.textContent = `アイテム ${item}`;
document.body.appendChild(el);
});
—
4. シニアからのアドバイス:なぜ「最適化」が必要なのか
このコードのポイントは、「ブラウザの機嫌を伺っている」ことだ。`requestIdleCallback`を使うことで、「今、描画の優先順位が高いか?」「シェフは暇か?」をブラウザ自身に判断させている。
現場で「パフォーマンスが悪い」と悩んだ時、まずはChromeの「Performanceタブ」を開いてくれ。
- 長いバー(Long Task)が立っていたら、それはシェフが一つ作業に夢中になりすぎて、他の客を無視している証拠だ。
- そのバーを切り刻んで、タスクを小さく分割する。これができるかどうかが、ジュニアとシニアの境界線だ。
最後に
ブラウザは魔法じゃない。極めて論理的で、かつ非情なシングルスレッドの実行環境だ。
俺たちが書くコードは、この孤独なシェフに「いかに効率よく、かつユーザーを待たせずに注文を捌いてもらうか」というパズルなんだ。
「とりあえず動けばいい」コードから卒業して、メインスレッドの呼吸を感じ取れるようになれば、君のプロダクトのUXは劇的に変わる。
分からないことがあればいつでも聞いてくれ。現場からは以上だ。

コメント