mytac/blogs

举个栗子详解事件代理

Opened this issue · 0 comments

mytac commented

为啥要使用事件代理

我们在开发中,可能会遇见这样的需求:

在一个列表中点击每个列表项,将每个列表项中的内容传入某个函数中进行处理。

通常结构为

    <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)来组织事件的冒泡传播。
img

使用事件代理

当子节点被点击时,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元素是没有响应的。