ブラウザの戻るボタンで戻ってきた時にもページの初期化を行うJS

# 概要

あるページから他のページに遷移し、ブラウザの戻るボタンで元のページに戻った時、画面の状態を初期化したいことがあります。
例えば、スイッチ系UIの選択状態を初期状態に戻したい時などです。
その際にも使える初期化コードです。

# サンプル

```javascript
// 初期化を求めるフラグを立てます
let isNeedInit = true;

function initPage() {
  // 初期化フラグが立っている場合にのみ実行します
  if (!isNeedInit) {
    return;
  }
  // 初期化フラグを折ります
  isNeedInit = false;

  /* ページの初期化処理をここに書きます */

};

// ページ離脱の際に初期化フラグを立て直します
window.addEventListener('pagehide', () => {
  isNeedInit = true;
});

// ページ再訪問時に初期化を求めます
window.addEventListener('pageshow', initPage);

// 最初の初期化を実行します
// 必要に応じてDOMContentLoaded等と連携してください
initPage();
```

# 解説

上記コードの`initPage`に初期化の処理を書くことで、ページの読み込み時や、戻るボタンでページに戻ってきた時に画面の初期化を行うことができます。

`window`の`pageshow`イベントはページを訪れた際に発火するイベントです。
以下にMDNの解説を引用します。

> * 最初にページを読み込んだとき
> * 同じウィンドウまたはタブの中で、他のページからそのページへ移動してきたとき
> * モバイル OS で凍結されたページを復元したとき
> * ブラウザーの進む、戻るボタンを利用してこのページに戻ったとき
> :::note info
> 最初にページを読み込んでいる間、 pageshow イベントは load イベントの後で発生します。

引用元：[Window: pageshow イベント - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/pageshow_event)

注意書きには`pageshowイベントはloadイベントの後で発生`と書かれています。
初期化処理を自前で実行した場合、`pageshow`イベントの発火時にも同じ処理が呼び出されてしまいます。

この重複処理を防ぐために`isNeedInit`フラグを利用し、`pagehide`イベントと連携して、最初の訪問時と他のページから戻ってきた際のみ処理するよう制限しています。

# スクリプトの処理順

ページ内のスクリプトは以下の順に処理されます。

1. `<script></script>`で読み込まれたスクリプト
1. Document:readystatechange(`document.readyState === 'interactive'`)
1. `<script src="defer.js" defer></script>`で読み込まれた外部スクリプト
1. Document:DOMContentLoaded
1. Window:DOMContentLoaded
1. Document:readystatechange(`document.readyState === 'complete'`)
1. Window:load
1. Window:pageshow

```html:ページ内のscript
<script>
function check(e) {
  console.log(e.type, e.currentTarget, document.readyState);
};

// ページ表示関連のイベント
// 発火順の確認のため、あえて逆の順序で記載しています
window.addEventListener('pageshow', check);
window.addEventListener('load', check);
window.addEventListener('DOMContentLoaded', check);
document.addEventListener('DOMContentLoaded', check);
document.addEventListener('readystatechange', check);
</script>
<script src="defer.js" defer></script>
<script src="plain.js"></script>
```

```javascript:defer.js
check({ type: 'defer.js', currentTarget: null });
```

```javascript:plain.js
check({ type: 'plain.js', currentTarget: null });
```

上記のHTMLを記述したページを読み込んだ場合、以下の結果になります。

```console:最初にページを表示した際のコンソール
plain.js null loading
readystatechange #document interactive
defer.js null interactive
DOMContentLoaded #document interactive
DOMContentLoaded Window interactive
readystatechange #document complete
load Window complete
pageshow Window complete
```

他のページに遷移した後、戻るボタンで戻ってきた際の挙動はブラウザによって異なります。

```console:Google Chrome, Microsoft Edge, キャッシュを無効化したブラウザ（最初の訪問時と同じ結果）
plain.js null loading
readystatechange #document interactive
defer.js null interactive
DOMContentLoaded #document interactive
DOMContentLoaded Window interactive
readystatechange #document complete
load Window complete
pageshow Window complete
```

```console:Firefox, Safari(MacOS), Safari(iOS)（pageshowイベントのみを処理）
pageshow Window complete
```

この他、`<script src="async.js" async></script>`で読み込むスクリプトは、上記の処理の間に割り込んで実行されます。
`async`属性をつけた場合、非同期に、HTMLの処理と並行してファイルの読み込みが行われ、読み込み完了後の実行可能なタイミングで処理されます。
順序の保証はありません。

# まとめ

解説は以上です。
`pageshow`,`pagehide`のイベントを活用するとブラウザの戻るボタンにも対応した処理を行うことができます。