寫在前面
DOM 文檔編程接口中並沒有為 DOM元素 提供原生的事件委托接口。對於 DOM 中的事件機制,原生 DOM 僅僅提供了事件模型的接口:事件監聽與處理。事件監聽與處理目前是有兩種方式實現,即 on{eventtype}
和 addEventListener
。具體參看上篇博客 最全的DOM事件筆記
事件委托思想是程序員在實際需求中發掘出來的。
在 原生DOM 提供的 DOM事件對象 的基礎上,有好幾種實現事件委托的方法。比如 JQuery 庫中封裝好的 on() 方法。那么下面介紹幾種在原生 DOM語法 中的事件委托的實現方法。
假設場景如下:
<div class="grandpa">
<div class="father">
<div class="child">
<span class="text">文字</span>
</div>
</div>
</div>
需求如下:
對兒子元素(.child)進行 click
事件監聽,當該元素被點擊時,在控制台輸出 我是兒子,我被點擊了! 。
1. 常見但錯誤的方法
const grandpaEl = document.getElementsByClassName('grandpa')[0];
grandpaEl.addEventListener('click', (e)=>{
const el = e.target;
if(el.className === 'child'){
console.log('我是兒子,我被點擊了!');
}
})
這樣得到的結果是,當點擊 .child 元素時會正確輸出,但當點擊 .child 里面的 .text 元素時不會正確輸出。
根據我們的實際需求,當點擊父元素包裹的子元素時,也是相當於點擊了父元素,應當觸發對應的事件處理函數。因此此種方法嚴格來說是錯誤的。
此種方法只適用於被監聽的元素沒有后代元素時使用。
2. 遞歸方法
const grandpaEl = document.getElementsByClassName('grandpa')[0];
grandpaEl.addEventListener('click', (e)=>{
let el = e.target;
while(!el.matches('.child')){
if(el === grandpaEl){
el = null;
break;
}
el = el.parentNode;
}
if(el){
console.log('我是兒子,我被點擊了!');
}
})
可以封裝成一個事件委托(代理)函數如下:
function delegate(agent, eventType, clientSelector, fn){
agent.addEventListener(eventType, (e)=>{
let el = e.target;
while(!el.matches(clientSelector)){
if(el === agent){
el === null;
break;
}
el = el.parentNode;
}
el && fn.call(el, e, el);
})
return agent;
}
3. 巧用事件對象的 path 屬性
const grandpaEl = document.getElementsByClassName('grandpa')[0];
grandpaEl.addEventListener('click', (e)=>{
let child = e.path.find(el => el.matches('.child'));
if(child){
console.log('我是兒子,我被點擊了!');
}
})