DOMイベントデリゲート
イベント・デリゲートは、祖先要素にイベント・リスナーを置くものです。
JSでのイベント委譲:イベントが発生したら、親要素にやりたいことを委譲します。
存在しない要素のクリックをリッスンしたい場合。先祖をリッスンして、リッスンしたい要素かどうかクリックを待つだけです。
ネイティブJSによるイベントデリゲートの実装
基本: 要素には、リスナー・イベントにバインドされた親要素があり、リスナー・イベント自体はありません。要素がクリックされると、親のリスナーイベントもトリガーされます。
例を挙げましょう:
これは4つのliを持つulです。
<ul id="ul">
<li id="li1">1</li>
<li id="li2">2</li>
<li id="li3">3</li>
<li id="li4">4</li>
</ul>
それぞれのliにリスナーイベントをバインドする必要があります。
li1.addEventListener('click', function() {})
li2.addEventListener('click', function() {})
li3.addEventListener('click', function() {})
li4.addEventListener('click', function() {})
これは、liの数が不確かで、実行される関数が同じである場合に特に厄介です。特に、liの数が不確かで、新しいliが追加された場合、新しいliがリスナーイベントにバインドされていないことは明らかです。
addButton.onclick = function(){
var li = document.createElement('li')
li.textContent = 'new'
document.querySelector('ul').appendChild(li)
}//新しいliを追加したが、リスニングイベントを自動的にバインドしなかった。
しかし、この例では、liがクリックされた場合、この時間もulをクリックしたのと同じではないでしょうか?
そのため、クリック・イベントを直接ulにバインドすることができます。
var ul = document.getElementById('ul')
ul.addEventListener('click', function() {})
親要素を聴くことで、子要素を聴くことが可能になるのですか?
これは正しくありません。ul.Paddingにpaddingを追加すると、ul.
ご覧のように、パディング部分がクリックされると、イベントもトリガーされます。リスナーがulだからです。
つまり、バグだらけのこのバインディング方法は、明らかに望ましい結果ではないので、イベントに判定を加えることができるのです:
クリックの対象を判断し、クリックが li にあればトリガーし、そうでなければトリガーしません。
ul.addEventListener('click', function(e) {
// イベントのソースをチェックする.targe要素がliかどうか
if (e.target && e.target.nodeName.toUpperCase == "LI") {
console.log("クリックに成功した");
}
}
ここで、上記のイベント受信プロパティなどを使用することになります:
target: イベントがトリガーされたときにクリックされた要素
currentTarget: イベントがトリガーされた時にリスニングしていたエレメント。
で、まだバグがあるの?あります!
試しにliにspanを追加してみてください:
<ul id="ul">
<li id="li1"><span>1</span></li> //最初のliにスパンを追加する
<li id="li2">2</li>
<li id="li3">3</li>
<li id="li4">4</li>
</ul>
この時点で、最初のliをクリックしてもbindイベントが発生しなくなりました。
親要素を聴くことが子要素を聴くことにつながると前述しましたが、子要素を聴くことは親要素を聴くことにつながります。
しかし、'padding'のバグを修正するために、タグ判定が追加され、'span'がbindイベントをトリガーしないようになりました。
if (e.target && e.target.nodeName.toUpperCase == "LI")
liの子孫があるかどうかを考えることは重要です。まず、クリックされた要素の先祖に li があるかどうかを判断し、もしあれば、クリックされた要素はやはり li です。
そのため、最終的に記述されるイベントデリゲート関数は次のように最適化されます。
ul.addEventListener('click', function() {
let el = e.target \実際にクリックされた要素を取得する
\クリックされた要素elの祖先にliがあるかどうかを判定する。
while (el && !el.matches(selector)) {
el = el.parentNode
if (element === el) {
el = null
}
}
if (el) {
console.log('コールバック関数を実行できる)
}
}
イベントデリゲートの利点
- リスナーの数を節約
- 動的に生成される要素をリッスン可能