はじめに
ふと、DOMとは何か理解したくなったので勉強してまとめたいと思います。
DOM全体だとかなり長くなってしまうので、今回はよくある下記のようなコードがどのような仕組みで動いているのか理解したいと思います。
let element = document.getElementById('el');
element.addEventListener('click', function() {})
DOM: Document Object Model
DOMは、HTMLやXMLを操作するためのインターフェイスです。
XMLに対してDOM操作って個人的には馴染みのないものですね。
Webページを表現するHTMLは、あくまで"文書"です。
DOMは、HTMLをオブジェクトとして操作・変更するためのインターフェイスであり、その操作を行う言語がJavaScriptです。
DOM操作で主に使用される言語がJavaScriptなだけであり、DOM操作は他の言語でも可能です。
例えばPHPには、DOM操作するためのクラスが用意されています。
https://www.php.net/manual/ja/book.dom.php
<?php
$doc = new DOMDocument();
$doc->loadHTML('<html><body id="test">Test<br></body></html>');
echo $doc->getElementById('test')->tagName; // body
?>
DOMインターフェイス
DOMで提供されるインターフェイスは様々なものがありますが、
いくつか取り上げてまとめていきたいと思います。
EventTarget
DOMを構成する基底のインターフェースです。
イベントを登録したり、DOM操作の対象になるオブジェクトです。
イベントを追加するaddEventListener()はEventTargetで実装されているメソッドです。
Node
DOMを構成するインターフェースの1つです。EventTargetのメソッドとプロパティを継承しています。
DOMのインターフェイスは、基本的にNodeを継承しています。
例えば以下のものはNodeに含まれます。
- HTMLElement
- テキスト
- コメント
- 属性
DOMは、HTML内のオブジェクトをNodeとして扱います。
以下のようなHTMLを分類してみるとこのような感じになると思います。
- html -> element node
- body -> element node
- h1 -> element node
- メイン -> テキスト node
- -> コメント node
<html>
<body>
<h1>タイトル</h1>
<!-- メイン -->
</body>
</html>
Document
DOM ツリーであるウェブページのコンテンツへのエントリーポイントとして働きます
https://developer.mozilla.org/ja/docs/Web/API/Document
<html>
から始まるDOMツリーにアクセスするための、エントリポイントです。
Nodeを継承していて、ドキュメント全体の情報を検索したりすることができます。
<html>
<body>
<h1>タイトル</h1>
<img src="hoge.jpg" alt="">
</body>
</html>
<script>
let images = document.images; // HTMLCollection[img]
</script>
DocumentやWindowオブジェクトは、Nodeを継承していて、EventTargetで実装されているaddEventListenerといったメソッドにアクセスすることができます。
Event Flow
インターフェイスを追っていくことで、addEventListenerが実装されている基底のインターフェイスまで学ぶことができました。
次に、イベント処理がどのように行われるのかみてみたいと思います。
イベントとは
そもそもイベントとは何か、簡単にまとめたいと思います。
Webページで行われる状態の変化や操作全般を指しています。
マウスカーソルの移動やボタンのクリック、動画の再生もイベントととして扱うことができます。
どんなイベントがあるか、こちらからわかりやすく一覧でみることができます。量は大変多いです。
WebVRに関連したイベントもありますね👀
https://developer.mozilla.org/ja/docs/Web/Events
イベントフロー
処理の流れを整理するためにイベントフローを理解するのが良さそうです。
画像は、本記事の参考文献でもあるW3Cのページからお借りしました。
https://www.w3.org/TR/DOM-Level-3-Events/
イベントが発生してからEventListenerの処理が行われるまで3つのフェーズがあります。
-
capture phase
WindowオブジェクトからターゲットとなるElementをツリー構造を下っていくように捕捉します。 -
target phase (at-target phase)
ターゲットを見つけたフェーズです。 イベントの種類をみてbubble phaseへ移すか判断します。 -
bubble phase
capure phaseとは逆に、イベントが起こったElementから親Nodeを辿ってイベントを伝搬させます。
イベントハンドラは基本的にバブリングフェーズに登録されるので、ユーザーが定義したハンドラの実行もこのフェーズで行われます。
伝播ということがどういうことかわかりづらかったので例に以下のようなコードを用意しました。
body
とh1
には同じハンドラを登録しています。
h1
をクリックした時に、consoleに2回以下のようなログが記録されるはずです。
[object HTMLHeadingElement] clicked
これはイベントのバブリングによってbodyに登録されたハンドラが実行されたことによります。
e.target
は、イベントが発生した要素を示すので、body
がクリックされた訳ではなく、h1
で発生したイベントが伝播したことがわかると思います。
<html>
<body id="body">
<h1 id="h1">タイトル</h1>
</body>
</html>
<script>
function handler(e) {
console.log(`${e.target} clicked`);
}
document.getElementById('body').addEventListener('click', handler);
document.getElementById('h1').addEventListener('click', handler);
</script>
なお、イベントの伝播をせずにターゲットのElementだけで処理をさせたいような場合には、e.stopPropagation()
を書いて上げることで実現可能です。
function handler(e) {
e.stopPropagation()
console.log(`${e.target} clicked`);
}
まとめ
冒頭に紹介したコードを改めて読んでまとめとします。
let element = document.getElementById('el');
element.addEventListener('click', function() {})
####el
がclickされた時の流れ
- クリックイベントが発生する
- イベントは、Windowオブジェクトから子Nodeに伝播する
- ターゲットとなる
el
を見つける - バブリングフェーズに登録された匿名関数を実行する
- (親Nodeへイベントが伝搬する)
参考
DOMの紹介
https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Introduction
DOM EventTarget
https://developer.mozilla.org/ja/docs/Web/API/EventTarget
DOM Node
https://developer.mozilla.org/ja/docs/Web/API/Node
ブラウザの仕組み全般
https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/#The_rendering_engine