O'Reilly「JavaScript第7版」を読んだ（15章編）

O'Reilly JavaScript 第 7 版を読んだので、自分の学習メモを主目的として気づきをまとめます。

https://www.oreilly.co.jp/books/9784873119700/

## 前置き

本投稿内容は第 15 章の内容アウトプットになります。

お仕事柄、第 15 章 「Web ブラウザ上の JavaScript」 のメモが多くなりそうなのでその手前までで分けることにしました。

https://qiita.com/kawa-mon/items/5803e4ef3f84efcb55bc

実務では TypeScirpt を使うエンジニアリングマネージャーをしていますが、体系立てて JavaScript を学んだことがほぼ無かったため、本書を手に取って、スキマ時間で読み進めてきました。今回を機に、新たに勉強になったこと・解釈間違いなどを整理してメモとしておきます。

## Web プログラミングの基礎

- 今や Web API のすべてを 1 冊の書籍でカバーするのは不可能なので、何か知りたいことがあれば [MDN Web docs](https://developer.mozilla.org/ja/) にあたるのが良い
- Web API の多くはブラウザの機能拡充に伴って非推奨になっているレガシーなものも含まれるので注意
- ブラウザのウィンドウやタブの HTML ドキュメントを表すのが Document オブジェクト
- HTML ドキュメントコンテンツを扱うための API が **DOM(Document Object Model)**
- JavaScript の実行は大きく 2 段階に分けられる
  1. ドキュメントが読み込まれる時
  1. 非同期でイベントドリブンなタイミング
     1. HTML ドキュメントが完全に読み込まれて解析された時の `DOMContentLoaded`
     1. 画像などドキュメントのすべての外部リソースも完全に読み込まれた時の `load`
     1. ...
- JavaScript はシングルスレッドなので、デッドロックや競合に悩む必要はない

## イベント

### イベントハンドラ（或いはイベントリスナー）

当書籍では、これらの用語を **イベントを処理する関数のこと** として同じ意味で用いていた。

> あるオブジェクトに足しいてイベントハンドラが呼び出されたとき、ブラウザがイベントを「発火した」、「トリガーした」、「ディスパッチした」と言うこともあります。

日々の会話で使っている言い回し、間違っていたかも。今後気をつけよう。

### addEventListener()

引数は 3 つ指定できる。

| 項目      | 説明                                                                    |
| :-------- | :---------------------------------------------------------------------- |
| 第 1 引数 | イベントハンドラを登録するイベントタイプを文字列形式で指定。            |
| 第 2 引数 | 第 1 引数に指定したイベントタイプが発生した時に呼び出される関数を指定。 |
| 第 3 引数 | 論理値またはオブジェクトを指定する。省略可能。詳細は後述。              |

```html
<button id="mybutton">Click me</button>
<script>
  let b = document.querySelector('#mybutton')
  b.addEventListener('click', () => {
    console.log('Thanks for clicking me!')
  })
</script>
```

第 3 引数に `true` を指定した場合は、イベントを **キャプチャリングする** イベントハンドラとして登録される。イベントキャプチャリングは後述。

論理値を渡す代わりに、オブジェクトを渡してオプションを明示的に指定することも可能。

| 項目               | 説明                                                                                         |
| :----------------- | :------------------------------------------------------------------------------------------- |
| capture プロパティ | _true_ の場合、イベントハンドラはキャプチャリングハンドラとして登録される。                  |
| once プロパティ    | _true_ の場合、イベントが 1 度発生した後、イベントリスナーは自動削除される。                 |
| passive プロパティ | _true_ の場合、デフォルトのアクションをキャンセルしない。（preventDefault() を呼び出さない） |

```js
document.addEventListener('click', handlerClick, {
  capture: true,
  once: true,
  passive: true,
})
```

### イベント伝播

イベント伝播は 3 つのフェーズがある。

1. キャプチャリングフェーズ
1. ターゲットオブジェクト自身のイベントハンドラ呼び出し
1. バブリングフェーズ

キャプチャリングフェーズは DOM ツリーを

1. Window オブジェクトのキャプチャリングイベントハンドラ
1. Document オブジェクトのキャプチャリングイベントハンドラ
1. ボディオブジェクトのキャプチャリングイベントハンドラ
1. ...
1. イベントターゲット親要素のキャプチャリングイベントハンドラ
   の順番で呼び出していく。

`addEventListener()` の第 3 引数を `true` にした場合や、第 3 引数で `{ capture: true }` を指定した場合はキャプチャリングイベントとして登録されるので、このフェーズで実行される。

バブリングフェーズはキャプチャリングフェーズの真逆の流れで DOM ツリーをさかのぼっていく。

このあたりの理解に際しては下記サイトに図解やサンプルコードがあり分かりやすかったので併せて掲載。

https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks/Events#other_event_concepts

https://qiita.com/hosomichi/items/49500fea5fdf43f59c58

### イベントのキャンセル

| メソッド            | 説明                                                                               |
| :------------------ | :--------------------------------------------------------------------------------- |
| `preventDefault()`  | チェックボックスのクリックなどデフォルト動作のキャンセル。                         |
| `stopPropagation()` | 上記イベント伝播を止める。キャプチャリングフェーズでもバブリングフェーズでも可能。 |

## カスタムイベントのディスパッチ

`dispatchEvent()` メソッドによって任意のイベントを発火でき、また`CustomEvent()` コンストラクタを使うことで、独自のオブジェクトをイベントオブジェクトのプロパティとして渡すことが出来る。

- `CusntomEvent()` の第 1 引数はイベントのタイプを表す文字列
- 第 2 引数には、イベントオブジェクトのプロパティとなる任意のオブジェクトを記載する

```js
document.dispatchEvent(new CustomEvent('busy', { detail: true }))

fetcu(url)
  .then(handleNetworkResponse)
  .catch(handleNetworkError)
  .finally(() => {
    document.dispatchEvent(new CustomEvent('busy', { detail: false }))
  })

document.addEventListener('busy', (e) => {
  e.detail ? showSpinner() : hideSpiner()
})
```

なるほど。jQuery の `trigger()` のようにイベント発火できるということか。

## ドキュメントの制御

### querySelectorAll()

戻り地は、Element オブジェクトの配列**ではない**。NodeList オブジェクトという配列のようなオブジェクト。反復可能なので for/of ループが使える。**NodeList を真の配列に変換するときは `Array.from()` を使って変換する。**

うん、確かによくやります、これ。

### データセット属性

名前が小文字で `data-` から始まる属性のこと。データセット属性は、要素の見た目には影響せず、どのような目的にも使用することが可能。DOM では、Element オブジェクトに`dataset`プロパティが定義されている。プロパティ名は、data-接頭辞を取り除いたものになり、`data-x`属性名にハイフンが含まれる場合はキャメルケースを使う。

```html
<h2 id="title" data-section-number="16.1">Attributes</h2>
```

```js
let number = document.querySelector('#title').dataset.sectionNUmber
```

## ドキュメントの位置とスクロール

要素の位置はドキュメントの左上角を基準とした X 座標・Y 座標で表す**ドキュメント座標**と、ビューポート（表示領域）の左上角を基準とした X 座標・Y 座標で表す**ビューポート座標**（ウィンドウ座標とも言う）の 2 つの異なる方法で表せる。

例えば、ドキュメント座標で X 座標が 200px の要素は、ユーザーがブラウザを下方向に 75px スクロールしている場合、ビューポート座標では Y 座標は 125px になる。Web ではスクロールできるドキュメント中に、スクロールできる要素を内包できるため、ドキュメント座標で表せないケースが有り、ビューポート座標を用いることが一般的。

要素の大きさとビューポート座標の位置を取得するには `getBoundingClientRect()` メソッドを呼び出すのが簡単。`left`, `right`, `bottom`, `width`, `height`　プロパティを持つオブジェクトを返す。

Element.scrollIntoView()メソッドを呼び出せば、その要素がビューポート中に表示されるようにスクロールする。デフォルトでは、要素がビューポートの上端付近に移動し、引数として `false` を指定すれば、ビューポートの下端付近に表示される。

https://developer.mozilla.org/ja/docs/Web/API/Element/scrollIntoView

## 出典

[David Flanagan 著、村上列 訳 『JavaScript 第 7 版』（オライリー・ジャパン）](https://www.oreilly.co.jp/books/9784873119700/)