addEventListener（removeEventListener、useCapture、stopPropagationなど）・カスタムイベント使い方　簡単なまとめ


# 一度だけイベントを実行したい
## 第三引数にonceプロパティがtrueのオブジェクトを指定
```js
document.addEventListener('click', function() {
    console.log('hello');
}, {
    once: true
});
```
最もシンプルですが、**IE、Edgeは対応していません。**

## イベントリスナにremoveEventListenerでそのリスナ自体を削除する
```js
document.addEventListener('click', function foo() {
    console.log('hello');
    this.removeEventListener('click', foo);
});
```
無名関数に名前を付けないと拾えないので、**名前は必須**です。
レガシーブラウザにも対応しています。

# イベントの伝播を制御したい
## 実行順を制御する
イベントは、イベントが発生した要素（**イベントターゲット**）から次の要素へと伝播します。
このイベントの流れを**イベントフロー**と呼びます。
イベントフローは次の3つのフェーズに分かれています。

- **キャプチャリングフェーズ**
windowオブジェクトを起点としてDOMツリーを下っていき、イベントターゲットまでのフェーズです。
**親要素から子要素**へと伝播します。
このフェーズで登録されたイベントリスナは、イベントターゲットのイベントリスナより先に実行されます。

- **ターゲットフェーズ**
イベントターゲットに登録されたイベントリスナが実行されるフェーズです。

- **バブリングフェーズ**
イベントターゲットからDOMツリーを上っていき、`window`オブジェクトまでのフェーズです。
**子要素から親要素**へと伝播します。
このフェーズで登録されたイベントリスナは、イベントターゲットのイベントリスナより後に実行されます。

**どのフェーズでイベントを実行するか制御するには、addEventListenerの第三引数（useCapture）に真偽値を設定**します。
**デフォルトではfalse**が設定されているので省略した場合はバブリングフェーズに登録されます。
`true`を設定するとキャプチャリングフェーズに登録されます。

イベントターゲットに登録されたイベントリスナはターゲットフェーズで実行されるためuseCaptureがtrueでもfalseでも関係ありません。

### 例
```html:html
<div id="foo">foo</div>
```
```js:js
var foo = document.getElementById('foo');

document.addEventListener('click', function() {
  console.log('document');
}, false);

foo.addEventListener('click', function() {
  console.log('foo');
}, false);
```
上記では文字列`foo`をクリックすると、

```text
foo
document
```
という順番で出力されます。

`document`に登録されているイベントリスナをキャプチャリングフェーズで実行されるように、第三引数を`true`にします。

```js:js
var foo = document.getElementById('foo');

document.addEventListener('click', function() {
  console.log('document');
}, true); // trueに変える

foo.addEventListener('click', function() {
  console.log('foo');
}, false);
```

この状態で`foo`をクリックすると

```
document
foo
```

と出力されます。

## イベントの伝播をストップする
イベントの伝播はイベントオブジェクトの**stopPropagationメソッド**でストップします。
先ほどの例で考えると、`foo`をクリックしたときに`document`と出力されないようにするには、`foo`のクリックイベントがバブリングしないようにする必要があります。

```js
document.addEventListener('click', function() {
  console.log('document');
}, false);

foo.addEventListener('click', function(e) {
  console.log('foo');
  e.stopPropagation(); // イベントの伝播ストップ
}, false);

// fooをクリックした出力結果
// foo
```

## イベントの伝播をストップし他のイベントリスナの呼び出しを防ぐ
**stopImmediatePropagationメソッド**を使うと、同じ要素に追加されていた他のイベントリスナの実行を防ぎます。

```js
document.addEventListener('click', function() {
  console.log('document');
}, false);

foo.addEventListener('click', function(e) {
  console.log('foo1');
  e.stopImmediatePropagation(); // イベントの伝播をストップし他のイベントリスナの呼び出しを防ぐ
}, false);

foo.addEventListener('click', function(e) {
  console.log('foo2');
}, false);

// fooをクリックした出力結果
// foo1
```
※stopPropagationメソッドでは`foo2`も出力されます。

## デフォルトのイベントの動作をキャンセルする
**preventDefault()メソッド**を使います。
`a`タグに対して、クリック時にページ遷移しないようにするためによく使います。
よく使うと思うのでサンプルコードは割愛。


# カスタムイベントを作る
1. イベントオブジェクト作成
2. 作成したイベントオブジェクトを初期化（作成したイベントオブジェクトのtypeによって若干異なる）
3. イベントリスナの登録 
4. イベント発行（dispatch）してイベントリスナ実行

## 1. イベントオブジェクト作成
```js
var customEvent = document.createEvent(type);
```
`createEvent`メソッドの引数には文字列でイベントタイプを指定します。
イベントタイプは数多くありますので下記参照。
[document.createEvent - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Document/createEvent#Notes)

## 2. 作成したイベントオブジェクトを初期化
```js
customEvent.initEvent(eventName, bubbles, cancelable);
```
| 引数 | 説明 |
|----|----|
|eventName| イベントの種類を表す文字列 |
|bubbles|バブリングを行うかの真偽値|
|cancelable|イベントがキャンセル可能かの真偽値|

イベントオブジェクト作成時にどのイベントタイプを指定したかによって初期化メソッドは変わります。
`initEvent`メソッドを使えるのは、`Event`や`HTMLEvents`イベントタイプの時です。

## 3. イベントリスナの登録 
```js
element.addEventListener(eventName, function() {
    // 実行するイベント
    console.log('カスタムイベント実行');
}, false);
```
引数`eventName`には、`initEvent`メソッドで指定したイベントの種類を指定します。

## 4. イベント発行（dispatch）してイベントリスナ実行
```js
target.dispatchEvent(customEvent);
```
最後にイベントターゲットの要素に対してイベント発行します。
`dispatchEvent`メソッドの引数には作成したイベントオブジェクトを渡します。
イベント発行するとすぐに対応するイベントリスナが実行されます。

## new Event()を使ったやり方
```js
// イベントオブジェクト作成
var customEvent = new Event(eventName);
// イベントリスナの登録
element.addEventListener(eventName, function() {
    console.log('カスタムイベント実行');
}, false);
// イベント発行
target.dispatchEvent(customEvent);
```
`new Event()`を使うと、`document.createEvent`メソッドでは必要だった`initEvent`メソッドでの処理が不要になり、ひと手間省けます。
しかし**IEでは使えません**。

## カスタムイベントを使って、状態に変化が生じたときにイベントを実行するサンプル
カスタムイベントを使うと、ユーザーの操作と表示を切り離すことができ、オブジェクト間の関係を疎結合にすることができます。コードの拡張や修正がしやすくなります。
自分がこれまでに使った例としては、一つの変数を監視して、その変数の内容に変化が加えられたら、表示を変えるというものがあります。
それを簡単に再現してみます。

```html:html
<button class="btn">100</button>
<button class="btn">200</button>
<button class="btn">300</button>
```
```js:js
'use strict';

/**
 * IE判定
 */
 function isIE() {
  var ua = navigator.userAgent.toLowerCase();
  return ((ua.indexOf('msie') > -1) && (ua.indexOf('opera') === -1)) || (ua.indexOf('trident') > -1);
}

/**
 * イベントオブジェクト作成
 * @param {String} eventName
 * @return {Object}
 */
 function makeEvent(eventName) {
  if (isIE()) {
    var ev = document.createEvent('Event');
    ev.initEvent(eventName, true, false);
    return ev;
  }
  return new Event(eventName);
}

/**
 * イベント発行
 * @param {HTMLElement} target
 * @param {Object} eventObj
 */
 function fireEvent(target, eventObj) {
  console.log('カスタムイベント実行！');
  target.dispatchEvent(eventObj);
}

/**
 * 状態管理の変数に値をセット
 * @param {*} value
 */
 function setData(value) {
  data.push(value);
  // イベント発行
  fireEvent(document, showDataEvent);
}

// 状態管理用変数
var data = [];

// カスタムイベント作成
var showDataEvent = makeEvent('showData');

var btns = document.querySelectorAll('.btn');

// カスタムイベントリスナ登録
document.addEventListener('showData', function() {
  console.log(data);
});

Array.prototype.slice.call(btns).forEach(function(btn) {
  btn.addEventListener('click', function() {
    setData(this.textContent);
  });
});
```
変数`data`に対して、`setData`ファンクションを使って値をセットしたのちにカスタムイベントを実行しています。
結果的に状態に変化が生じたときにイベントを発行しているという意味合いになります。
これだけだとあまりカスタムイベントの効果が得られにくいですが、もっと規模が大きくなるとその効果は大きく感じられます。

# 参考
- [EventTarget.addEventListener - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)
- [三章第二回　イベントリスナ — JavaScript初級者から中級者になろう — uhyohyo.net](https://uhyohyo.net/javascript/3_2.html)
- [javascriptのカスタムイベントを作ろう！ - tumblr](http://shim0mura.hatenadiary.jp/entry/20111224/1324735206)
- [JSでカスタムイベントを作る - はらへり日記](http://sota1235.hatenablog.com/entry/2015/12/01/101358)
- [JavaScriptのカスタムイベント - console.lealog();](http://lealog.hateblo.jp/entry/2014/01/11/193457)
- [イベントの作成と発火 - ウェブデベロッパーガイド | MDN](https://developer.mozilla.org/ja/docs/Web/Guide/Events/Creating_and_triggering_events#The_old-fashioned_way)