JavaScriptのpreventDefault()って難しくない？preventDefault()を使うための前提知識

preventDefault()って難しくないですか？

何かを妨害しているということはわかるのですが、何を妨害しているのか、
どういうときに指定するべきなのかといった部分が自分の中で曖昧で、
最近「なんなんだ、e.preventDefault()って」と思うことがありました。

いろいろ勉強した後で「preventDefaultがよくわからなかったのは、この前提知識がなかったからだ」
ということに気づいたため、その知識を自分用に備忘録として書きました。

preventDefault()の使い方がわからなかったり、
「なんかpreventDefaultが効いてない気がする」
といったことに悩んでいる人は読んでみてください。

#知識①イベント
DOM内の各要素では、頻繁に「イベント」が発生しています。
有名な例では、aタグでclickイベントが発生していたり、formでkeydownイベントが発生したりしています。

100個以上のDOMイベントがありますが、[^1]
まずpreventDefault()を理解する上で、このイベントを2つに分けましょう。
「ユーザーが発動させるイベント」と、「ブラウザが勝手に発動させるイベント」です。


#### A.ユーザーが発動させるイベントの例
・click　PCでは頻繁に発生させています
・keypress　キー入力の際に発生するイベントです
・touchmove　スマホを使っている人は日常的に大量に発動させています

#### B.ブラウザが勝手に発動させるイベントの例
・DOMContentLoaded　ブラウザによりHTMLの解析が完了した場合に勝手に発動します
・ended　見慣れないかもしれませんが、audioやvideoが再生終了したときに勝手に発動します。
・error　よく見ますよね。通信エラーやJavaScriptエラーが発生した際に勝手に発動します。

このうち、preventDefault()をよく使うのは「A」の方です。[^2]

clickやkeydown、touch関連のイベントに対してイベントリスナを設定する場合に、
preventDefault()の設定を検討することになります。

特定の対象[^特定の対象]に対してイベントリスナを設定するときは、下記のようなコードを書きます。

```js
// ここでは「myButton」に対して設定している。
// myButton上でclickイベントが発生した時に実行される処理（関数）を、第二引数に渡すことができる。
myButton.addEventListener( 'click', function(e){
  e.preventDefault(); // clickイベントに関して「preventDefault」する
});
```

#知識②イベントのデフォルト動作
「prevent default」を直訳すると、「デフォルトの動作を発生させない」ということになります。
そのため、イベントの「デフォルトの動作」が何なのかがわかっていないと、
「preventDefault()を設定したほうがいいのか？しないほうがいいのか？」がわかりません。
私もここで悩みました。

#### イベントのデフォルト動作例
| イベント | ブラウザのデフォルト動作例 |
|:-:|:-:|
|「touchmove」|スマホの画面がスクロールされる|
|「aタグ」での「click」|aタグのherfで指定されたURLへ遷移する|
|「formのチェックボックス」での「click」|チェックボックスのオン/オフが切り替わる|
|テキストタイプの「form」での「keypress」|文字が入力される|
|inputの「submit」での「click」| actionで指定されたURLへ遷移＋データ送信|

これらブラウザのデフォルト動作を認識していないと、
「何がpreventされるのか」がわかりません。

touchmoveのように、ほぼ常にデフォルト動作が発生するようなイベントもありますし、
（あなたも、スマホの画面を触って、よくスクロールしてますよね）
同じイベントでも常には動作が発生しないものもあります。

例えばURL遷移は、__aタグ、またはinput[type=submit]のボタンをクリックしないと__発生しませんし、
文字入力に関しては__type=textであるformにフォーカスをあてた状態でキーボードを入力しないと__発生しません。

preventDefault()を使用する上では、このイベントのブラウザデフォルト動作を知った上で、
__それを妨害したいのかどうか__を考える必要があります。[^3]

#知識③passive: false

最近のブラウザでは、preventDefaultを正常に使うために、
イベントリスナに{ passive: false }を設定する必要があります。

下記コードでは、フォームで発生する「keypress」イベントに対しイベントリスナを設定しています。
preventDefault()の効果により、アルファベットの小文字以外はフォームに手打ちできません。
（MDNからの流用です）

```html
<p>あなたの名前を小文字のみで入力してください。</p>
<form>
<input type="text" id="my-textbox" />
</form>

<script>
var myTextbox = document.getElementById('my-textbox');
myTextbox.addEventListener( 'keypress', checkName, {passive: false} );

function checkName(e) {
  var charCode = e.charCode;

  if (charCode != 0) {
    if (charCode < 97 || charCode > 122) {
      e.preventDefault();
      alert("小文字のみ入力できます。");
    }
  }
}
</script>
```
上記コードを見ると、addEventListenerの第三引数で{ passive: false } を設定しています。
これを設定しないと、一部のイベントでpreventDefault()が効かないことがあります。[^4]

passiveは「受け身」という意味なのですが、
passiveが「false」ということは、
「このイベントリスナは受け身ではない」
＝「このイベントリスナはイベントのデフォルト動作を妨害するかもしれない」
＝「だからブラウザは文字入力の実行を待たなければいけない」という意味になります。

passiveを「true」にすると、「このイベントリスナは受け身である＝デフォルトの動作を妨害しない」とブラウザに解釈され、preventDefault()が無視されて文字が普通に入力されます。

なんでこんな設定があるんだと思われるかもしれませんが、
{ passive: true }にしておくことで
ブラウザは次の動作（スクロールや文字入力など）をさっさと実行できる
＝ブラウザの実行速度があがるというメリットがあります。

でも「passive: false」という記述は分かりづらいですね。
「hasPreventDefault: true」とかだとわかりやすいんですけどね。

# まとめ
上記3つが、preventDefaultを理解するのに必要（だと思った）前提知識です。
同じような誰かの助けになれば嬉しいです。


[^1]: https://developer.mozilla.org/ja/docs/Web/Events
[^特定の対象]: addEventListener()を設定する対象としてよくあるものは Element, Document, Window といったオブジェクトです。「イベント」に対応したあらゆるオブジェクトを対象とすることができます (XMLHttpRequest など)。https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener
[^2]: イベントのコールバック関数の中で「console.log(e.cancelable)」を確認すれば、そのイベントがpreventDefaultできるのかどうかわかります。なお、「A」に属するイベントでもpreventDefaultできないものもあります。
[^3]: ここを考えるのが一番難しいと思います。
[^4]: 試したのですが、このkeypressの場合はfalseを設定しなくてもコールバック内のpreventDefaultが効きました。[macOS 10.13.4 Chrome/Safari最新版] touchmoveイベントなどでは、falseを設定しないとpreventDefaultが無視されるようですね。(https://qiita.com/shge/items/d2ae44621ce2eec183e6)