为啥要使用事件代理
我们在开发中,可能会遇见这样的需求:
在一个列表中点击每个列表项,将每个列表项中的内容传入某个函数中进行处理。
通常结构为
<ul id="list">
<li>msg</li>
<li>msg1</li>
<li>msg2</li>
<li>msg3</li>
</ul>
这里需要点击某个列表项,弹出他的内容文本,不难写出下面的方法:
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:在调用一次后被移除
}
}
然而,当我们对这个大列表进行dom操作时,比如添加一个节点,但上面的事件并没有绑定到这个新节点上,需要我们再次调用这个函数,重新遍历这些子节点,绑定事件。
了解事件捕获和冒泡
事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
事件冒泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。
使用事件代理
当子节点被点击时,click事件向上冒泡,父节点捕获到事件后,我们判断是否为所需的节点,然后进行处理。代码如下:
const ulNode = document.getElementById("list")
ulNode.addEventListener('click',(e)=>{
// e.target为当前元素的子节点,这里只对li元素进行处理
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)
}
不需要再次为新节点绑定事件,照样会响应。
当我们把新建的子节点改成p元素时,
const newLi=document.createElement('p')
因为做了判断,所以点击p元素是没有响应的。