一、是什麼#
事件代理,俗地來說,就是把一個元素響應事件(click
、keydown
......)的函數委託到另一個元素
前面講到,事件流的都會經過三個階段: 捕獲階段 -> 目標階段 -> 冒泡階段,而事件委託就是在冒泡階段完成
事件委託,會把一個或者一組元素的事件委託到它的父層或者更外層元素上,真正綁定事件的是外層元素,而不是目標元素
當事件響應到目標元素上時,會通過事件冒泡機制從而觸發它的外層元素的綁定事件上,然後在外層元素上去執行函數
下面舉個例子:
比如一個宿舍的同學同時快遞到了,一種笨方法就是他們一個個去領取
較優方法就是把這件事情委託給宿舍長,讓一個人出去拿好所有快遞,然後再根據收件人一一分發給每個同學
在這裡,取快遞就是一個事件,每個同學指的是需要響應事件的 DOM
元素,而出去統一領取快遞的宿舍長就是代理的元素
所以真正綁定事件的是這個元素,按照收件人分發快遞的過程就是在事件執行中,需要判斷當前響應的事件應該匹配到被代理元素中的哪一個或者哪幾個
二、應用場景#
如果我們有一個列表,列表之中有大量的列表項,我們需要在點擊列表項的時候響應一個事件
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
如果給每個列表項一一都綁定一個函數,那對於內存消耗是非常大的
// 獲取目標元素
const lis = document.getElementsByTagName("li")
// 循環遍歷綁定事件
for (let i = 0; i < lis.length; i++) {
lis[i].onclick = function(e){
console.log(e.target.innerHTML)
}
}
這時候就可以事件委託,把點擊事件綁定在父級元素ul
上面,然後執行事件的時候再去匹配目標元素
// 給父層元素綁定事件
document.getElementById('list').addEventListener('click', function (e) {
// 兼容性處理
var event = e || window.event;
var target = event.target || event.srcElement;
// 判斷是否匹配目標元素
if (target.nodeName.toLocaleLowerCase === 'li') {
console.log('the content is: ', target.innerHTML);
}
});
還有一種場景是上述列表項並不多,我們給每個列表項都綁定了事件
但是如果用戶能夠隨時動態的增加或者去除列表項元素,那麼在每一次改變的時候都需要重新給新增的元素綁定事件,給即將刪去的元素解綁事件
如果用了事件委託就沒有這種麻煩了,因為事件是綁定在父層的,和目標元素的增減是沒有關係的,執行到目標元素是在真正響應執行事件函數的過程中去匹配的
舉個例子:
下面html
結構中,點擊input
可以動態添加元素
<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
<li>item 4</li>
</ul>
使用事件委託
const oBtn = document.getElementById("btn");
const oUl = document.getElementById("ul1");
const num = 4;
//事件委託,添加的子元素也有事件
oUl.onclick = function (ev) {
ev = ev || window.event;
const target = ev.target || ev.srcElement;
if (target.nodeName.toLowerCase() == 'li') {
console.log('the content is: ', target.innerHTML);
}
};
//添加新節點
oBtn.onclick = function () {
num++;
const oLi = document.createElement('li');
oLi.innerHTML = `item ${num}`;
oUl.appendChild(oLi);
};
可以看到,使用事件委託,在動態綁定事件的情況下是可以減少很多重複工作的
三、總結#
適合事件委託的事件有:click
,mousedown
,mouseup
,keydown
,keyup
,keypress
從上面應用場景中,我們就可以看到使用事件委託存在兩大優點:
- 減少整個頁面所需的內存,提升整體性能
- 動態綁定,減少重複工作
但是使用事件委託也是存在局限性:
-
focus
、blur
這些事件沒有事件冒泡機制,所以無法進行委託綁定事件 -
mousemove
、mouseout
這樣的事件,雖然有事件冒泡,但是只能不斷通過位置去計算定位,對性能消耗高,因此也是不適合於事件委託的
如果把所有事件都用事件代理,可能會出現事件誤判,即本不該被觸發的事件被綁定上了事件