吉本集の個人ブログ
Web制作の技術について書いています。たまに日記も書きます。

【JS入門】イベントについて Vol.1(イベントハンドラの登録、イベント伝播)

2018年2月9日 / category : javascript

イベントについて解説していきます。イベントとは、ユーザーがボタンをクリックしたり、入力フォームにテキストを入力したりなどのユーザー操作によるアクション、画像や外部ファイルの読み込によるネットワーク、ブラウザの状態の変化、動画やオーディオなどの操作・再生状況によるアプリケーションの状態の変化、タイマーやエラー処理の非同期プログラムモデルなど、ブラウザで起こるあらゆる変化に対し発生する通知(できごと・きっかけ)です。
これらの通知の種類(イベントタイプ)を文字列で分類します。例えば、ユーザーがボタンをクリックするというイベントは、clickというイベントタイプになります。ユーザーがマウスを動かすというのもイベントで、mousemoveというイベントタイプです。
イベントが通知されると処理が実行されます。この処理を実行させる命令(関数)を、イベントハンドラまたはイベントリスナーと呼びます。イベントハンドラは関数ですので、イベントが発生する前に定義しておくことができます。

イベントハンドラとイベントリスナーの用語を分けて使う場合があります。イベントハンドラとは、先ほど解説した通りイベントが通知されるタイミングで実行される関数です。それに対して、イベントリスナーはその関数(イベントハンドラ)を登録する仕組みを指して使用する場合があります。

イベントハンドラを登録する

このイベントハンドラは、EventTargetインターフェースで定義しているメソッドaddEventListenerで登録します。
EventTargetインターフェースは、過去の記事「DOMについて Vol.1(DOM、インターフェース)」で触れていますが、全てのDOMオブジェクトが継承しているインターフェースです。

EventTarget.addEventListener(eventType, listener, useCapture):イベントターゲットにイベントハンドラ(イベントリスナー)を登録する。
引数 eventType(string) : イベントタイプ
引数 listener(function) : イベントハンドラ(イベントリスナー)
引数 useCapture(boolean) : キャプチャフェーズの設定
構文

EventTarget.addEventListener('click', function(){ console.log('クリックしました'); }, false);

次のコードを見てみます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タイトル</title>
</head>
<body>
<button id="btn">ボタン</button>
<script src="./js/sample.js"></script>
</body>
</html>
sample.js
function message(){
	console.log('クリックしました。'); // クリックしました。
};
let btn = document.getElementById('btn');
btn.addEventListener('click', message);

ブラウザ上にあるボタンをクリックするとコンソール画面に「クリックしました。」と表示されます。イベントハンドラを登録した簡単な例です。イベントハンドラは複数登録することができます。

sample.js
function message(){
	console.log('クリックしました。'); // クリックしました。
};
function hunger(){
	console.log('お腹減った。'); // お腹減った。
};
let btn = document.getElementById('btn');
btn.addEventListener('click', message);
btn.addEventListener('click', hunger);

ボタンをクリックすると「クリックしました。」「お腹減った。」の順に表示されます。これはイベントハンドラの登録順になります。

イベント伝播(イベントバブリング)について

上のメソッドaddEventListenerの説明で、第3引数にキャプチャフェーズの設定の論理値があります。先ほどのコードでは第3引数は設定していませんでしたが、これは省略形で初期値のfalseが設定されている状態です。falseということは、キャプチャフェーズの設定をしないということになります。次のコードを見てみます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タイトル</title>
</head>
<body>
<div id="inner">
<p id="p"><button id="btn">ボタン</button></p>
</div>
<script src="./js/sample.js"></script>
</body>
</html>
sample.js
let html = document.documentElement;
let body = document.body;
let inner = document.getElementById('inner');
let p = document.getElementById('p');
let btn = document.getElementById('btn');
window.addEventListener('click', function(){ console.log('window'); }, false);
document.addEventListener('click', function(){ console.log('document'); }, false);
html.addEventListener('click', function(){ console.log('html'); }, false);
body.addEventListener('click', function(){ console.log('body'); }, true);
inner.addEventListener('click', function(){ console.log('inner'); }, false);
p.addEventListener('click', function(){ console.log('p'); }, false);
btn.addEventListener('click', function(){ console.log('btn'); }, false);

コンソール画面

btn
p
inner
body
html
document
window

ボタンをクリックすると上のような順でコンソールが表示されます。続いて、第3引数のキャプチャフェーズの設定をtrueに変更して再度ボタンをクリックしてみます。

sample.js
let html = document.documentElement;
let body = document.body;
let inner = document.getElementById('inner');
let p = document.getElementById('p');
let btn = document.getElementById('btn');
window.addEventListener('click', function(){ console.log('window'); }, true);
document.addEventListener('click', function(){ console.log('document'); }, true);
html.addEventListener('click', function(){ console.log('html'); }, true);
body.addEventListener('click', function(){ console.log('body'); }, true);
inner.addEventListener('click', function(){ console.log('inner'); }, true);
p.addEventListener('click', function(){ console.log('p'); }, true);
btn.addEventListener('click', function(){ console.log('btn'); }, true);

コンソール画面

window
document
html
body
inner
p
btn

第3引数のキャプチャフェーズの設定をtrueにすると、コンソールへの出力順が逆になりました。それではキャプチャフェーズとは何でしょうか。

イベントが発生(発火)すると、DOMツリー構造をバブリングします。バブリングとは、イベントの伝播のことです。イベントを発生させた要素からその親要素、先祖要素にバブリングされ、Documentオブジェクト、さらにWindowオブジェクトにまで進みます。ただし、このバブリングは3番目のフェーズになります。

イベントが発生(発火)すると、3つの「フェーズ」が発生します。

1. キャプチャフェーズ(Capture Phase)
2. ターゲットフェーズ(Target Phase)
3. バブリングフェーズ(Bubble Phase)

先ほど説明したバブリングのフェーズは上のように3番目に発生します。最初はキャプチャフェーズのフローが発生します。これがメソッドaddEventListenerの第3番目の引数で設定する論理値でtrueを設定すると、そのイベントはキャプチャフェーズのイベントハンドラとして登録されます。ではキャプチャフェーズとはどんなものでしょうか。

キャプチャフェーズ(Capture Phase)

上の図のように、ボタンをクリックするとWindowオブジェクトから順に発生させた要素の親要素までイベントがバブリングされます。これがキャプチャフェーズです。キャプチャフェーズが完了すると、次はターゲットフェーズのフローが発生します。

ターゲットフェーズ(Target Phase)

ターゲットフェーズは、イベントターゲット自身のイベントハンドラを実行するフェーズになります。これが2番目に発生するフェーズですが、もしキャプチャフェーズで登録したイベントハンドラがなければ、このフェーズが1番目ということになります。続いて、最後のフェーズは、バブリングフェーズになります。

バブリングフェーズ(Bubble Phase)

バブリングフェーズは最後のフェーズです、これは先ほど解説した通り、イベントを発生させた要素からその親要素、先祖要素にバブリングされ、Documentオブジェクト、さらにWindowオブジェクトにまで進みます。

以上が、イベント伝播(イベントバブリング)についての仕組みになります。次のコードでイベント伝播(イベントバブリング)がどのように動作するか確認してみてください。

sample.js
let html = document.documentElement;
let body = document.body;
let inner = document.getElementById('inner');
let p = document.getElementById('p');
let btn = document.getElementById('btn');
window.addEventListener('click', function(){ console.log('window'); }, true);
document.addEventListener('click', function(){ console.log('document'); }, false);
html.addEventListener('click', function(){ console.log('html'); }, true);
body.addEventListener('click', function(){ console.log('body'); }, false);
inner.addEventListener('click', function(){ console.log('inner'); }, true);
p.addEventListener('click', function(){ console.log('p'); }, false);
btn.addEventListener('click', function(){ console.log('btn'); });

コンソール画面

window
html
inner
btn
p
body
document

上のコンソール結果は想像できたでしょうか。キャプチャフェーズとしてイベントハンドラを設定したオブジェクトが、「window」「html」「inner」となりますので、1番目のフェーズとして順に呼ばれます。続いて「btn」はターゲットフェーズになりますので、2番目のフェーズになります。最後にバブリングフェーズが発生し、「p」「body」「document」と呼ばれます。

次回は、イベント伝播を止めたい場合(イベントのキャンセル)について解説したいと思います。