前置き
JavaScript がいつ実行されるか? を理解すると 初期化やイベントハンドリングを書くときに困らないための知識になる。
よく質問されるけど細かいものは毎回調べるので整理したメモ。
関連記事
🧱 ブラウザの基礎(Rendering Pipeline)
-
ブラウザレンダリングの仕組み(基礎編)
DOM / CSSOM / Render Tree の基礎まとめ -
ブラウザレンダリング(内部構造と最適化)
メインスレッド / コンポジタ / ラスタ / GPU
⚙️ JavaScript と非同期処理
-
JavaScript の実行順
JS が「どの順番で実行されるか」を理解する -
小学生にもわかるかもしれない同期・非同期・Promise
同期と非同期をわかりやすく説明
🚀 パフォーマンスと UX
-
ユーザー中心のパフォーマンス モデル RAIL
UX の体感速度を設計する枠組み -
Core Web Vitals
Google が推す“実測体験”の評価指標
JavaScript が実行される主要なタイミング
読み込み時 ざっくり実行順
- 直ちに実行
- readyState: loading
- readyState: interactive
- defer
- DOMContentLoaded
- readyState: complete
- load
- pageshow(BFCache)
全体の流れ:直ちに実行 → (内部 script の場合 / 外部 script の実行開始時)パース停止 → readyState: loading → interactive → defer実行
→ DOMContentLoaded → readyState: complete → load → pageshow
読み込み時に「いつ JS が実行されるか?」
-
直ちに実行
-
<script>タグに書かれたコードは読み込まれたとき即座に実行される。 -
ブラウザは
<script>に遭遇すると必ず HTML のパーサー(解析)を止める。
(DOM を壊しうるので結果を待つ必要があるため)※JavaScript は DOM を変更できるので、“構築途中の DOM の状態” と矛盾が起きないよう、パーサーは JavaScript の実行結果を必ず待つ必要がある。
-
内部 script → その場で即実行してから解析再開
-
外部 script(デフォルト / async・defer なし)はダウンロード中は非同期で、パースが継続し、ダウンロード完了後にパースを一度停止させて実行される。
-
-
readystatechangeイベント(loading)- DOM の構築がまだ途中
-
readystatechangeイベント(interactive)- DOM 構築が終了し、ユーザー操作が可能
-
scriptロード属性によるタイミング(
defer)-
defer属性:DOM 構築後に実行。DOMContentLoaded の直前に必ず終わる
-
-
DOMContentLoadedイベント- HTMLの解析・DOMツリーの構築が完了したタイミング
- 画像やCSSのロードは待たない。
-
readystatechangeイベント(complete)- 外部リソース(画像・CSS等)もすべて読み終えた
-
loadイベント- ページ内の画像やスタイルシートなど全ての外部リソースが読み込み終わった後に発火。
-
pageshowイベント- ページが「表示された」タイミングで発火。初回ロード直後にも、Back/Forward Cache(BFCache)から復帰した場合にも発生する。
-
event.persistedがtrueの場合、BFCache からの復帰であることを示す。
-
pagehideイベント- ページが「非表示になる」タイミングで発火。通常のページ遷移だけでなく、ページが BFCache に保存される前にも発生する。
-
event.persistedがtrueの場合、ページが破棄されず BFCache に保存されたことを示す。
-
scriptロード属性によるタイミング(
async)-
async属性:スクリプトが読み込み次第すぐに実行(HTML解析とは非同期)。実行順は不安定で制御に不向き。
-
読み込み時以外
-
beforeunload/unloadイベント- ページ遷移や閉じる直前に発生。
-
ユーザー操作イベント
-
click、scroll、resize、keydown、keyupなど。
-
-
タイマーによる遅延実行
-
setTimeoutやsetIntervalを使った遅延/定期的な実行。
-
※HTML パースとは関係なく、ユーザー操作やタイマーでも JS は実行される。
readystatechangeについて
readystatechange イベントはドキュメントの状態が変わるタイミング(loading→interactive→complete)
サンプル
document.onreadystatechange = () => {
if (document.readyState === "interactive") {
// DOM構築完了時の処理(DOMContentLoadedの代わりに)
initApp();
} else if (document.readyState === "complete") {
// ページ読み込み完了時の処理(loadの代わりに)
finishApp();
}
};
readyState と DOMContentLoaded は似たタイミングだが、readyState はブラウザ内部の状態フラグ、DOMContentLoaded はイベントとして通知されるもの。
ほぼ同じ瞬間をさすけど微妙に違うもの
-
readystatechange: interactiveとDOMContentLoadedブラウザは DOM 構築が完了した瞬間にdocument.readyState = "interactive"へ切り替える→この後(わずか後)に イベントキューへ DOMContentLoaded が投入される
- interactive = DOM が組み終わった瞬間のステータス変更
- DOMContentLoaded = 「DOM構築完了」を通知するイベント
-
readystatechange: completeとloadすべての外部リソース(画像・CSS など)が読み終わったとき、document.readyState が"complete"になる→この後にload イベントが発火する
- complete は状態フラグを切り替える瞬間
- load はその完了を通知するイベント
async属性とdefer属性
JavaScript のscriptタグは、見つけたら即実行 → HTML パース停止という非常に重い挙動をする。
async / defer はその “重さ” をどう扱うかのために存在している。
async:[読み込み]→ 完了した瞬間に実行(順序は不定)
defer:[読み込み]→ DOM 構築後に実行(順序保証あり)
async 属性
読み込み完了したスクリプトが、HTML パースに割り込んで即実行される。
✔ 仕組み
- HTML パースを止めずに バックグラウンドでダウンロード
- ダウンロード完了した瞬間、その場で強制実行(パースは中断)
- 他の script より早く読み終わったら、容赦なく先に走る
✔ メリット
- HTML パースを邪魔しない → 初期表示(FCP)が速い
- 実行順に依存しない “単独で動く JS” に最適(例:広告タグ、計測タグ、SNS埋め込み、外部ウィジェット)
✔ デメリット
- 実行タイミングが「読み込み速度」に依存するため、DOM の準備状況が安定しない → 依存関係がある JS では使えない(実行順序が保証されない)
✔ パフォーマンスでの立ち位置
- CRP をほぼ邪魔せずに読み込める
- “パースブロック”を避けることで TTI(初期応答性) が改善される
- 初期表示に関係ないスクリプトを async にすれば、メインスレッドを奪わずにページが表示される
defer 属性
バックグラウンド読み込み → DOM 完成後に “書かれた順番どおり” 実行される。
✔ 仕組み
- HTML パースを止めずに 非同期ダウンロード
- DOM 構築完了後(=パース終了後)に実行
- 複数ある場合は 記述順に実行
- DOMContentLoaded の直前までに終わる
✔ メリット
- HTML パースを邪魔しない → 初期表示が速い
- DOMContentLoaded の直前に必ず終わるため、安定した制御ができる(順序保証)
- インタラクティブ前の初期化とも相性が良い
✔ デメリット
- DOM 必須の JS でないと defer の恩恵は少ない
- “DOM 完成後に実行” が前提なので、一部の初期化が遅い場合もある
✔ パフォーマンスでの立ち位置
- CRP を阻害せずに ページ本体の JS を読み込める
- script タグによる “レイアウトのブロック” がなくなり、FCP(First Contentful Paint)を改善できる
- async と違い 実行順が保証されるため、ページ本体の JS に最適
async / defer の使い分け(最短で判断できる版)
- async:ページ本体と関係ない JS(広告 / SNS / アナリティクス)
- defer:ページ本体に必要な JS(アプリロジック / UI 初期化)
- 違い:実行タイミングと制御性
なぜ async / defer が CRP に影響するか
<script>はデフォルトでは HTML パースを止める(=同期実行)。
JavaScript は実行結果が DOM を変更する可能性があるため、HTML パーサーは途中で止まって JS の結果を待つ必要がある。
async / defer はこの“パースブロック”を解除できるため、初期表示の高速化に直結する。
JS の実行が Layout/Paint をブロックする例:
// Layout を強制する(読み取り)
const height = element.offsetHeight;
// すぐ書き込み
element.style.height = height + 10 + 'px';
JS の実行と Rendering(Layout/Paint)の関係
JavaScript と Rendering(Style / Layout / Paint)はどちらもメインスレッドで動く。
そのため、JS の実行タイミングは Rendering の進行に直結する。
- JS が長く実行されている間、Layout/Paint はブロックされる
- HTML パース中の
<script>は DOM の構築を止める - DOM やスタイルを JS から読むと Layout が強制発生(forced reflow)
- Layout が発生すると Paint → Composite も連鎖して動く
- 結果として、FPS が落ちる・INP が悪化する・初期描画が遅れる
script の読み込み(async / defer)はこの レンダリング との競合をどれだけ避けられるか。
補足: ちょいちょいでてくるBFCacheとはなにか?
BFCache(Back/Forward Cache)は、ブラウザが「戻る/進む」で高速復帰できるように、ページ全体の状態を丸ごと保存しておく仕組み
ページ遷移時に破棄されず、スナップショットとして保持される
そのため復帰時は pageshow(persisted: true)が発火する。
補足: 実行順とWebパフォーマンス指標・UX/SEOの関係
Webパフォーマンス指標とscript実行順の関係
FCP
対策: deferやasyncの併用、描画前に必要なscriptのみ同期実行
INP
対策:重いJS処理の分割/遅延化、ユーザー操作に直結するJSの優先配置
CRP
対策: CSSは先読み、JSはdeferで並列化、不要資産を後ろへ
UXとSEOへの具体的な影響
UX(ユーザー体験)
Webパフォーマンス指標が悪化すると「重い・遅い・動かない」といった体験を生み、直帰率や満足度に直結
SEO(検索順位)
Google Core Web Vitalsは直接ランキングに反映。
script順序・読み込み戦略の最適化はSEO対策にもなる。
まとめ
-
<script>は基本的に読み込まれた瞬間に実行され、HTML パースを止める - readyState は状態変化、DOMContentLoaded / load はイベント
- readyState は loading → interactive → complete の順で変化する
- DOMContentLoaded は DOM 構築完了、load はリソース読み込み完了
- BFCache 復帰時は pageshow(persisted: true)が発火する
- async / defer を使うと HTML パースをブロックしない(初期表示高速化の要)
- async は「読み込み次第すぐ実行」、defer は「DOM 完成後に順番を守って実行」
- 実務的には「不要な JS を後ろへ」「本体 JS を defer」