在JavaScript中,事件委托Event delegation是一種事件的響應(yīng)機(jī)制,當(dāng)需要監(jiān)聽(tīng)不存在的元素或是動(dòng)態(tài)生成的元素時(shí),可以考慮事件委托。
事件委托得益于事件冒泡(有關(guān)事件冒泡可以參考事件冒泡與事件捕獲),當(dāng)監(jiān)聽(tīng)子元素時(shí),事件冒泡會(huì)通過(guò)目標(biāo)元素向上傳遞到父級(jí),直到document,如果子元素不確定或者動(dòng)態(tài)生成,可以通過(guò)監(jiān)聽(tīng)父元素來(lái)取代監(jiān)聽(tīng)子元素。
舉個(gè)例子:
假設(shè)頁(yè)面從存在一個(gè)ul,其子元素動(dòng)態(tài)生成.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>事件委托</title>
</head>
<body>
<button>點(diǎn)擊</button>
<ol></ol>
<script>
let btn = document.getElementsByTagName('button')[0]
let ol = document.getElementsByTagName('ol')[0]
btn.addEventListener('click',function(){
let li = document.createElement('li')
let number = parseInt(Math.random() * 100, 10)
li.textContent=number
ol.appendChild(li)
})
<script>
</body>
</html>
當(dāng)點(diǎn)擊按鈕時(shí),ol列表中動(dòng)態(tài)創(chuàng)建li,如果給li添加click事件,當(dāng)li被點(diǎn)擊時(shí),li本身被刪除,那么可以這樣實(shí)現(xiàn)
btn.addEventListener('click',function(){
let li = document.createElement('li')
let number = parseInt(Math.random() * 100, 10)
li.textContent=number
ol.appendChild(li)
li.addEventListener('click',function(){
li.remove();
})
})
但是這樣就會(huì)使得給每個(gè)li都添加了click事件(或者其他的事件),而頁(yè)面中的li是不確定有多少的,如果頁(yè)面中生成很多的li,那么就會(huì)造成內(nèi)存占用過(guò)多,因此我們可以使用事件委托來(lái)解決這個(gè)問(wèn)題。
/*
li.addEventListener('click',function(){
li.remove();
})
*/
ol.addEventListener('click',function(e){
let ele = e.target
if(ele.tagName==='LI'){
ele.remove()
}
})
在這個(gè)例子中,通過(guò)給父級(jí)而非子集添加事件,當(dāng)事件被拋到更上層的父節(jié)點(diǎn)的時(shí)候,我們通過(guò)檢查事件的目標(biāo)對(duì)象target來(lái)判斷并獲取事件源li,當(dāng)子節(jié)點(diǎn)被點(diǎn)擊的時(shí)候,click事件會(huì)從子節(jié)點(diǎn)開(kāi)始向上冒泡。父節(jié)點(diǎn)捕獲到事件之后,通過(guò)判斷e.target.nodeName來(lái)判斷是否為我們需要處理的節(jié)點(diǎn)。并且通過(guò)e.target拿到了被點(diǎn)擊的Li節(jié)點(diǎn)。從而可以獲取到相應(yīng)的信息,并作處理。
但是當(dāng)li中有其他的標(biāo)簽時(shí)如span標(biāo)簽(或是其他的標(biāo)簽),直接使用上述方法就會(huì)導(dǎo)致事件無(wú)法被正確觸發(fā)。
btn.addEventListener('click',function(){
let li = document.createElement('li')
let span = document.createElement('span')
let number = parseInt(Math.random() * 100, 10)
span.textContent=number
li.appendChild(span)
ol.appendChild(li)
ol.addEventListener('click',function(e){
let ele = e.target
if(ele.tagName==='LI'){
ele.remove()
}
})
})
此時(shí),當(dāng)點(diǎn)擊span時(shí),li并未被刪除,點(diǎn)擊span與li之間的部分li才會(huì)被刪除,試想當(dāng)子元素li中有多層嵌套時(shí),就會(huì)導(dǎo)致事件無(wú)法被正確觸發(fā),因此我們需要對(duì)li進(jìn)行判斷,判斷點(diǎn)擊的是否是ol下的li,否則就繼續(xù)查找,直到查找到li。
btn.addEventListener('click',function(){
let li = document.createElement('li')
let span = document.createElement('span')
let number = parseInt(Math.random() * 100, 10)
span.textContent=number
li.appendChild(span)
ol.appendChild(li)
ol.addEventListener('click',function(e){
let ele = e.target
while(ele.tagName!=='LI'){
if(ele===ol){
ele=null; break;
}
ele=ele.parentNode
}
ele && ele.remove()
})
})
此時(shí)不論li嵌套多少層,li的click事件都會(huì)被正確觸發(fā)。
事件委托可以通過(guò)監(jiān)聽(tīng)父級(jí)來(lái)達(dá)到監(jiān)聽(tīng)子級(jí)的效果,減少了監(jiān)聽(tīng)器的數(shù)量,使用內(nèi)存也相對(duì)減少。當(dāng)在一些難以追蹤的情況下,從DOM從刪除的元素仍保留「記憶」(即泄露),這些泄露的「記憶」通常與事件綁定聯(lián)系在一起,使用事件委托,可以隨意的對(duì)子元素進(jìn)行事件綁定而不用擔(dān)心忘記解綁它們的監(jiān)聽(tīng)器。
參考: