JavaScriptのページ読み込み関連イベントの発火順序をまとめてみた

## はじめに
JavaScriptを学習していると、Webページを読み込むタイミングで処理を行うことがよくあります。

- loadイベント（window.onload）
- DOMContentLoadedイベント
- defer属性を付与したscript要素

これらはブラウザのWebページ読み込み処理と関係しています。
しかし個別では理解できても、まとめると処理の順序がなかなかイメージできず、一覧にしてまとめた表があったらわかりやすいのに、と思ったので勉強がてら作ってみました。

## 対象者
- ブラウザのページ読み込みとイベントの発火タイミングがイメージできない方
- ページ読み込みに関するJavaScriptのイベント一覧が知りたい方
- ページ読み込みに関する流れが知りたい方

## 前提と結論
前提として、Webページが表示されるまでの大まかな流れは以下のようになっています。

1. HTTPレスポンスによりブラウザがhtmlファイルを受け取る
2. 受け取ったhtmlファイルを元にブラウザがレンダリング処理を行う
3. Webページが表示される

この記事は上記の２から３へ向かう流れを「Webページの読み込み」として、解説したものです。

結論として、処理の順番とブラウザの状態、発火するイベントや処理の流れを一覧にまとめたものが以下になります。

|順番|ブラウザの状態|htmlの読み込み状態|発火するイベント・実行される処理|
|--|--|--|--|
|0|HTTPレスポンスによるhtmlファイルの受け取り|-|-|
|1|htmlファイル読み込み開始|loading|-|
|2|htmlファイル読み込み中|loading|外部リソースの取得・通常のscript要素の実行|
|3|htmlファイル読み込み完了|interactive|readystatechangeイベント|
|4|htmlファイル読み込み完了|interactive|defer属性の付与されたscript要素の実行|
|5|htmlファイル読み込み完了|interactive|DOMContentLoadedイベント|
|6|外部リソース読み込み開始|interactive|画像の表示等|
|7|外部リソース読み込み完了（Webページの表示完了）|complete|readystatechangeイベント|
|8|外部リソース読み込み完了（Webページの表示完了）|complete|loadイベント|

処理の流れについて、実際に確認したい方は[こちらのサンプルコード(Plunker)](https://plnkr.co/edit/see5pBorlvfm9SzK?p=preview&preview)が非常に参考になります。
[サンプルコード出典：JAVASCRIPT.INFO](https://ja.javascript.info/onload-ondomcontentloaded#ref-911)

それでは次項からこの表に至る過程について説明していきます。


※ページの読み込みについてはブラウザの**レンダリング**が大きく関わっています。
レンダリングについて、この記事で直接は触れませんが、理解するのにとても役立ったので調べてみるのはオススメです（参考にしたサイトのリンクは[最後](#参考にしたサイト)にまとめてあります）。

## JavaScriptのページ読み込み関連イベント
はじめに、JavaScriptではページの読み込みに関連して、以下のようなイベントが発火します。

- [loadイベント](https://developer.mozilla.org/ja/docs/Web/API/Window/load_event)
- [DOMContentLoadedイベント](https://developer.mozilla.org/ja/docs/Web/API/Window/DOMContentLoaded_event)
- [readystatechangeイベント](https://developer.mozilla.org/ja/docs/Web/API/Document/readystatechange_event)
（リンク先はMDNのページです）

詳細についてはそれぞれのページを参照してもらうとして、それぞれのイベントの発火タイミングは以下の通りとなります。

|イベント名|発火タイミング|
|--|--|
|DOMContentLoaded|htmlファイルの読み込みが完了したタイミング|
|load|htmlファイルと外部リソースの読み込みがどちらも完了したタイミング|
|readystatechange|htmlファイルの読み込み状態が変化したタイミング|
※外部リソースとはhtmlファイル内でsrc属性をつけて記述する、CSSや画像ファイルなどの外部ファイルのことです。

このうちDOMContentLoadedイベントとloadイベントについては、

- 画像の読み込みを待たない→DOMContentLoadedイベント
- 画像の読み込みを待つ→loadイベント

としてよく比較されており、「画像（外部リソース）の読み込みを待たない分、DOMContentLoadedイベントの方が先に処理される」というような相対的な処理順序での紹介をよく見かけます。

一方、readystatechangeイベントは、「htmlファイルの読み込み状態」の変化を検知するもので、前の２つとは異なり発火タイミングが複数あります。

この「htmlファイルの読み込み状態」は、前の２つのイベントとも関係する、ブラウザでの処理状況を表すものになります。

## 「htmlファイルの読み込み状況」とは
前項で、readystatechangeイベントを「htmlファイルの読み込み状態の変化を検知するイベント」と紹介しました。
ここでは「htmlファイルの読み込み状態」がなんなのかについて説明します。

「htmlファイルの読み込み状態」とは、ブラウザがhtmlファイル読み込み時にとる３つの状態のことを表します。
「htmlファイルの読み込み状態」は以下の３つです。

|htmlファイルの読み込み状態|詳細|
|--|--|
|loading|htmlファイルの読み込み開始〜完了までの状態|
|interactive|htmlファイルの読み込みが完了した状態|
|complete|htmlファイル及び外部リソースの読み込みがどちらも完了した状態|

これら３つの状態を表す値は[document.readyStateプロパティ](https://developer.mozilla.org/ja/docs/Web/API/Document/readyState)で取得できます。
つまり、readystatechangeイベントはこのプロパティの値の変化を検知して発火するイベントです。


## 「読み込み状態」と読み込み関連イベントとの関係
さて、前項で説明した「読み込み状態」のうちinteractiveとcompleteの「詳細」の内容について、似たような記述が前の表にも出てきました。

DOMContentLoadedイベントと、loadイベントです。

この２つのイベントは、以下のように対応しており、状態の切り替わりと**ほぼ同時※**に発火するイベントです。
- DOMContentLoadedイベントの発火 ≒ interactive状態への切り替わり
- loadイベントの発火 ≒ complete状態への切り替わり
※イベントの発火よりも読み込み状態の切り替わりの方がわずかに早い。

これらを１つの表にまとめると以下のようになります。

htmlファイルの読み込み状態（window.readyStateの値）|読み込み状態が切り替わった時に発火するイベント|詳細|
|--|--|--|
|loading|-|htmlファイルの読み込み開始〜完了までの状態|
|interactive|DOMContentLoadedイベント, readystatechangeイベント|htmlファイルの読み込みが完了した状態|
|complete|loadイベント, readystatechangeイベント|htmlファイル及び外部リソースの読み込みがどちらも完了した状態|

ここまでで、JavaScriptのページ読み込み系のイベントと、ブラウザによるhtmlファイルの読み込み処理がどのように対応しているのかについて説明しました。

続いて、ここにdefer属性を付与したscript要素の実行タイミングも加えて考えていきます。

## defer属性を付与したscript要素の実行タイミング
defer属性をscript要素に付与すると、記述された処理の実行タイミングが変わります。
defer属性を付与しない場合、ブラウザによるhtmlファイルの読み込みがscript要素に届いた時点で実行されますが、defer属性を付与していると以下のように変わります。
>この論理属性は、スクリプトを文書の解析完了後かつ DOMContentLoaded が発生する前に実行することをブラウザーに示します。
defer属性の付いたスクリプトは、スクリプトが読み込まれて評価が完了するまで、 DOMContentLoadedイベントの発生が抑制されます。
~中略~
defer属性のあるスクリプトは、文書に現れた順で実行されます。

[出典：\<script>: スクリプト要素 - HTML: HyperText Markup Language | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/script)

ここでいう「文書の解析完了」というのは、これまで出てきた「htmlファイルの読み込み完了」と同義で、読み込み状態がinteractiveへと切り替わるタイミングです。
つまりdefer属性のついたスクリプトの実行タイミングは以下の順番になります。

1. interactive状態への切り替わり
2. defer属性の付与されたscript要素の実行（複数の場合は上から順に実行）
3. DOMContentLoadedイベントの発火

## まとめ
それでは最後に、これまで出てきたイベントや処理が発生する順番をまとめてみます。
（冒頭に出てきたものと同じ表です）

|順番|ブラウザの状態|htmlの読み込み状態|発火するイベント・実行される処理|
|--|--|--|--|
|0|HTTPレスポンスによるhtmlファイルの受け取り|-|-|
|1|htmlファイル読み込み開始|loading|-|
|2|htmlファイル読み込み中|loading|外部リソースの取得・通常のscript要素の実行|
|3|htmlファイル読み込み完了|interactive|readystatechangeイベント|
|4|htmlファイル読み込み完了|interactive|defer属性の付与されたscript要素の実行|
|5|htmlファイル読み込み完了|interactive|DOMContentLoadedイベント|
|6|外部リソース読み込み開始|interactive|画像の表示等|
|7|外部リソース読み込み完了（Webページの表示完了）|complete|readystatechangeイベント|
|8|外部リソース読み込み完了（Webページの表示完了）|complete|loadイベント|

[非常に参考になるサンプルコード(Plunker)](https://plnkr.co/edit/see5pBorlvfm9SzK?p=preview&preview)
[サンプルコード出典：JAVASCRIPT.INFO](https://ja.javascript.info/onload-ondomcontentloaded#ref-911)

## おわりに
ページ読み込みとイベントの関係については、レンダリングについて知ってから曖昧だった部分の理解が一気に進みました。
レンダリングはややこしく、まだまだ知識は浅いのですが、ブラウザの仕組みやはたらきが以前よりもずっと理解できるようになりました。
この記事が同じような悩みを持っている方の参考になれば幸いです。
もし記事の内容に誤りやアドバイス等あればコメントいただけると嬉しいです。


## 参考にしたサイト
（★がついているのは特に勉強になったと感じたサイトです）

- レンダリングについて
	- ★[ブラウザのしくみ: 最新ウェブブラウザの内部構造 - HTML5 Rocks](https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/#The_browser_main_functionality)
	- [ウェブサイトが表示されるまでにブラウザはどういった仕事を行っているのか？ - GIGAZINE](https://gigazine.net/news/20190624-how-web-works/#16)
		- ★[クリティカル レンダリング パスの測定  |  Web  |  Google Developers](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp?hl=ja)
	- [vasanthk / how-web-works：ブラウザにwww.google.comと入力すると、舞台裏で何が起こりますか？](https://github.com/vasanthk/how-web-works)
	- [【図解/ブラウザの仕組み】DOMとパースとレンダリング,asyncとdeferについて | SEの道標](https://milestone-of-se.nesuke.com/sv-basic/web-tech-basic/html-javascript-dom-parse-rendering/)
	- [フロントエンジニアなら知っておきたいブラウザレンダリングの仕組みをわかりやすく解説！ | LeapIn](https://leap-in.com/ja/lets-learn-how-to-browser-works/)
	- [ブラウザレンダリングの仕組み - Qiita](https://qiita.com/sasakiki/items/91dcc8b50d7a61ce98bc)
		- [クリティカル レンダリング パス  |  Web  |  Google Developers](https://developers.google.com/web/fundamentals/performance/critical-rendering-path?hl=ja)
	- [ブラウザレンダリング入門〜知ることで見える世界〜 - Qiita](https://qiita.com/codemafia0000/items/caed57ec30d638e40728)
	- [Webパフォーマンスカレンダー»重要なレンダリングパスの解読](https://calendar.perfplanet.com/2012/deciphering-the-critical-rendering-path/)
- ページ読み込みに関するイベント
	- ★[ページのライフサイクル: DOMContentLoaded, load, beforeunload, unload](https://ja.javascript.info/onload-ondomcontentloaded)
	- [Document: DOMContentLoaded イベント - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Document/DOMContentLoaded_event)
	- [Window: load イベント - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/load_event)
	- [Document: readystatechange イベント - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Document/readystatechange_event)
- defer属性
	- [\<script>: スクリプト要素 - HTML: HyperText Markup Language | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/script)
	- ★[\<script> タグに async / defer を付けた場合のタイミング - Qiita](https://qiita.com/phanect/items/82c85ea4b8f9c373d684)
		- [HTML Standard](https://html.spec.whatwg.org/multipage/scripting.html#attr-script-async)
- パフォーマンス
	- [Webパフォーマンス虎の巻 - Qiita](https://qiita.com/usagi-f/items/10f35969f08dd01fa635#fn1)