まず「シングルスレッド」のイメージから
JavaScript が「シングルスレッド」というのは、
「同時に実行できるのは、基本的に1つのことだけ」 という意味です。
console.log()を実行しているあいだは、他の関数は同時には動かない- 一個の処理が終わってから、次の処理が始まる
画面のクリック処理も、タイマーの処理も、あなたの書いた関数も、
ぜんぶ「1本の道」に並んで順番に流れていきます。
ここが重要です。
JavaScript は「一人で全部やる店員さん」スタイル。
その代わり、後ろで「手伝ってくれる人(ブラウザやOSの機能)」をうまく使うことで、
「シングルスレッドだけどノンブロッキング」という動きを実現しています。
なぜブラウザの JavaScript はシングルスレッドで始まったのか(歴史と事情)
元々「小さなスクリプト」からスタートした
JavaScript が作られた当初(1995年ごろ)、
想定されていたのは、次のような用途でした。
- ボタンを押したときにちょっとした処理をする
- 簡単なフォームチェックをする
今みたいな「巨大なアプリ」ではなく、
「HTMLにちょこっと動きをつける程度」 の役割だったんです。
そのときに大事にされたのは、
- ブラウザ側・実装者側から見てシンプルであること
- Web デザイナーや初学者でも簡単に触れること
でした。
マルチスレッドはパワフルですが、
- ロック(同時アクセスを防ぐ仕組み)
- デッドロック(お互い待ち合って詰む)
- 共有データの整合性
などの難しい問題がつきまといます。
この複雑さを、初めての Web スクリプト言語に持ち込みたくなかった、
という背景があります。
Web ページの世界では「安全性」が最優先だった
ブラウザの中には、
- DOM(画面の要素)
- cookie
- localStorage
など、ユーザーの情報や状態に関わるものがたくさんあります。
これらを複数スレッドで同時に触り始めると、
- どのスレッドがいつ DOM をいじるのか
- 更新の途中の不整合状態が見えてしまわないか
- 競合(同時更新)をどう防ぐのか
といった問題が一気に難しくなります。
シングルスレッドなら、
「いつ DOM が書き換わるか」は“実行中のその1本のコードの中だけ”で完結します。
ブラウザ実装者にとっても、セキュリティや安定性を確保しやすい設計でしたr。
ここが重要です。
ブラウザの JavaScript は、
「安全でシンプルな UI スクリプト」としてスタートしたからこそシングルスレッド。
そしてその設計が、今もなお引き継がれている、という流れです。
「シングルスレッドなのに非同期」ができる理由
1本しかないのに、なぜ「同時に動いてるように見える」のか
ここが多くの人が一度つまずくポイントです。
ポイントは、
「JavaScriptエンジン自体は1本だけど、その外側に“手伝い要員”がいる」
という構造にあります。
ブラウザ環境だと、
- JavaScriptエンジン(シングルスレッド)
- Web API(タイマー、DOM、XHR/fetch などを担当するブラウザ側の機能)
- タスクキュー(「終わったらこれ実行してね」と並べておく箱)
- イベントループ(キューから順に仕事を持ってくる係)
という役者が協力して動きます。
簡単な具体例:setTimeout の裏側で起きていること
console.log("A: 開始");
setTimeout(() => {
console.log("B: 3秒後の処理");
}, 3000);
console.log("C: 同期の処理");
JavaScriptこのときの流れをざっくり言うと:
- JavaScript エンジンが “A” を出力(メインスレッド)
setTimeoutを呼ぶと、「3秒後にこの関数を実行して」と Web API に依頼して、メインスレッドの仕事は終了- メインスレッドはすぐに次に進み、”C” を出力
- 3秒後、Web API が「終わったよ、このコールバック実行して」とタスクキューに追加
- イベントループが、メインスレッドが暇になったタイミングで、そのコールバックを実行
- “B” が出力される
この間ずっと JavaScript が待ち続けているわけではなく、
待つのはブラウザ側の仕組み(Web API)。
JavaScript 本体は「次の行」にとっとと進むわけです。
ここが重要です。
「シングルスレッドだけど非同期」は、JavaScriptエンジン+ホスト環境(ブラウザ/Node)+イベントループの連携で実現している、という構造の理解が重要です。
マルチスレッドじゃないことの「メリット」と「トレードオフ」
メリット1:言語としての複雑さがずっと低くなる
マルチスレッドになると、
- 2つのスレッドが同時に同じオブジェクトを触るかもしれない
- 片方が書き換えている途中に、もう片方が読みに来るかもしれない
- それを防ぐために「ロック」「ミューテックス」「セマフォ」などが必要になる
といった問題が出てきます。
シングルスレッドなら、
- 常に「今動いているコードは1箇所だけ」
- コールスタックも1本だけ
- 共有データの同時アクセスをほぼ心配しなくていい
というシンプルさがあります。
初心者にとってはこれは大きなメリットで、
「同期と非同期を理解すれば、スレッドやロックは一旦忘れても進める」 という状況を作ってくれます。
メリット2:DOM 操作などの UI 更新が直感的
ブラウザで DOM を操作するとき、
document.querySelector("#msg").textContent = "変更";
JavaScriptというコードがあったとして、
これを複数スレッドが同時に触ったらどうなるか?
…と考えなくていいのは、シングルスレッドだからです。
「今この瞬間、DOM を書き換えているのは世界に1箇所だけ」という前提で設計できるので、
ブラウザ側の実装も、あなたのコードもシンプルになります。
トレードオフ:重い同期処理に弱い(だから非同期が大事)
もちろん、デメリットもあります。
1本しかないメインスレッドを、長時間占有するような処理を書くと、
- その間、クリックやスクロールに反応しなくなる
- アニメーションがカクつく、固まる
という「ブロッキング」の問題がすぐに表面化します。
だから JavaScript では、
- ネットワーク処理は非同期
- ファイル I/O(Node.js)は非同期
- タイマーも非同期
という設計が基本になっていて、
重い処理をできるだけメインスレッドから逃がす・分割する という発想がとても重要になります。
「じゃあマルチスレッドは一切ないの?」という疑問
Web Workers / Worker Threads という「別スレッド」は存在する
「シングルスレッド」とはいっても、
ブラウザや Node.js には、別スレッドを使う機能もあります。
- ブラウザ:Web Workers
- Node.js:Worker Threads
これらは「別スレッドで JavaScript を動かす」仕組みですが、
メインスレッドとはメモリ空間を共有しない(基本はメッセージでやりとり) という制約があります。
つまり、
- メインの JavaScript 実行モデル(DOM さわるところ)はシングルスレッドを維持
- 重い計算などを Worker に投げて、メインスレッドのブロッキングを避ける
という「折衷案」のような世界観になっています。
メインスレッドはあくまで1本、というのが大原則
重要なのは、
- メインの JS 実行(イベントハンドラ、UI 更新など)は1本のスレッド
- Worker は「別プロセス的な立場」で、メッセージ経由で協力する
という構造が変わっていないことです。
これにより、
- UI 更新の一貫性
- DOM 操作の安全性
- 言語仕様としてのシンプルさ
を保ったまま、「どうしても必要なところだけ別スレッドの力を借りる」ことができます。
例題で感覚を固める:シングルスレッドだからこうなる
例1:関数の呼び出し順は必ず「一列」
function first() {
console.log("first start");
second();
console.log("first end");
}
function second() {
console.log("second start");
third();
console.log("second end");
}
function third() {
console.log("third");
}
first();
JavaScript出力は必ずこの順になります。
- first start
- second start
- third
- second end
- first end
途中に他の関数が割り込んだり、並行して third が動いたりはしません。
これは「コールスタックが1本しかない」=シングルスレッドの直接的な結果です。
例2:重い while があると、そのあいだイベントハンドラは動けない
const btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
console.log("クリック!");
const start = Date.now();
while (Date.now() - start < 3000) {
// 3秒間ブロッキング
}
console.log("3秒後に終わった");
});
JavaScriptこのボタンを連打しても、
1回目のクリックの while が終わるまで、2回目以降のクリックイベントは処理されません。
- イベントはキューに溜まる
- メインスレッドが暇になったら順に処理される
という形になるので、メインスレッドを長時間塞ぐと「後続のイベント」が全部遅れます。
まとめ:JavaScript がシングルスレッドである理由を一文で
最後に、ぎゅっとまとめるとこうです。
JavaScript は、ブラウザ上の安全でシンプルな UI スクリプトとして生まれたため、「DOM や共有状態を複数スレッドで触る複雑さ」を避け、1 本のメインスレッドで動く設計が採用された。
その代わり、イベントループと非同期 API を駆使することで、「シングルスレッドなのにノンブロッキング」という実行モデルを実現している。
あなたがこれから学ぶ
- イベントループ
- コールスタック
- タスクキュー
- Promise / async/await
といった概念は、全部この
「1本しかないメインスレッドを、いかに詰まらせずにうまく回すか」
というテーマにつながっています。
この「JavaScript はあえてシングルスレッドを選んでいる」という土台が腑に落ちると、
非同期処理の世界全体がだいぶクリアに見えてくるはずです。
