為啥要使用事件代理
我們?cè)陂_(kāi)發(fā)中,可能會(huì)遇見(jiàn)這樣的需求:
在一個(gè)列表中點(diǎn)擊每個(gè)列表項(xiàng),將每個(gè)列表項(xiàng)中的內(nèi)容傳入某個(gè)函數(shù)中進(jìn)行處理。
通常結(jié)構(gòu)為
<ul id="list">
<li>msg</li>
<li>msg1</li>
<li>msg2</li>
<li>msg3</li>
</ul>
這里需要點(diǎn)擊某個(gè)列表項(xiàng),彈出他的內(nèi)容文本,不難寫(xiě)出下面的方法:
window.onload = () => {
const ulNode = document.getElementById("list")
const liNodes = ulNode.childNode || ulNode.children
for (let i = 0; i < liNodes.length; i++) {
liNodes[i].addEventListener('click', (e) => {
alert(e.target.innerHTML)
}, {
once: true
}) // once:在調(diào)用一次后被移除
}
}

然而,當(dāng)我們對(duì)這個(gè)大列表進(jìn)行dom操作時(shí),比如添加一個(gè)節(jié)點(diǎn),但上面的事件并沒(méi)有綁定到這個(gè)新節(jié)點(diǎn)上,需要我們?cè)俅握{(diào)用這個(gè)函數(shù),重新遍歷這些子節(jié)點(diǎn),綁定事件。

了解事件捕獲和冒泡
事件捕獲:當(dāng)某個(gè)元素觸發(fā)某個(gè)事件(如onclick),頂層對(duì)象document就會(huì)發(fā)出一個(gè)事件流,隨著DOM樹(shù)的節(jié)點(diǎn)向目標(biāo)元素節(jié)點(diǎn)流去,直到到達(dá)事件真正發(fā)生的目標(biāo)元素。在這個(gè)過(guò)程中,事件相應(yīng)的監(jiān)聽(tīng)函數(shù)是不會(huì)被觸發(fā)的。
事件目標(biāo):當(dāng)?shù)竭_(dá)目標(biāo)元素之后,執(zhí)行目標(biāo)元素該事件相應(yīng)的處理函數(shù)。如果沒(méi)有綁定監(jiān)聽(tīng)函數(shù),那就不執(zhí)行。
事件冒泡:從目標(biāo)元素開(kāi)始,往頂層元素傳播。途中如果有節(jié)點(diǎn)綁定了相應(yīng)的事件處理函數(shù),這些函數(shù)都會(huì)被一次觸發(fā)。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來(lái)組織事件的冒泡傳播。

使用事件代理
當(dāng)子節(jié)點(diǎn)被點(diǎn)擊時(shí),click事件向上冒泡,父節(jié)點(diǎn)捕獲到事件后,我們判斷是否為所需的節(jié)點(diǎn),然后進(jìn)行處理。代碼如下:
const ulNode = document.getElementById("list")
ulNode.addEventListener('click',(e)=>{
// e.target為當(dāng)前元素的子節(jié)點(diǎn),這里只對(duì)li元素進(jìn)行處理
if(e.target&&e.target.nodeName.toLowerCase()==='li'){
alert(e.target.innerHTML)
}
})
const btn = document.getElementById('btn')
btn.onclick = () => {
const newLi=document.createElement('li')
const text=document.createTextNode("new msg")
newLi.appendChild(text)
ulNode.appendChild(newLi)
}

不需要再次為新節(jié)點(diǎn)綁定事件,照樣會(huì)響應(yīng)。
當(dāng)我們把新建的子節(jié)點(diǎn)改成p元素時(shí),
const newLi=document.createElement('p')

因?yàn)樽隽伺袛?,所以點(diǎn)擊p元素是沒(méi)有響應(yīng)的。