DOM事件委托详解

173 阅读3分钟

前文讲到了DOM事件代理:冒泡与捕获

DOM事件委托

事件委托就是把事件监听放在祖先元素(如父元素,爷爷元素)上。

JS里的事件委托:当事件触发时,把想做的事情委托给父元素处理

如果要监听不存在的元素的点击事件。监听祖先,等点击的时候看看是不是我想要监听的元素即可。

使用原生 JS 实现事件委托

基础:一个元素的父元素绑定了监听事件,而本身没有绑定监听事件。如果该元素被点击,也会触发父元素的监听事件

让我们举个例子:

这是一个ul,里面有4个li

    <ul id="ul">
        <li id="li1">1</li>
        <li id="li2">2</li>
        <li id="li3">3</li>
        <li id="li4">4</li>
    </ul>

现在要给每个li绑定一个监听事件

li1.addEventListener('click', function() {})
li2.addEventListener('click', function() {})
li3.addEventListener('click', function() {})
li4.addEventListener('click', function() {})

如果执行的函数都一样,且li个数很多,这就显得非常麻烦了。尤其是li个数不确定的时候,此时我们新增一个li,很显然,新增的li并没有绑定监听事件。

addButton.onclick = function(){
  var li = document.createElement('li')
  li.textContent = 'new'  
  document.querySelector('ul').appendChild(li)
}//新增了li,但没有自动绑定监听事件

但是,在此例子中,如果点击了li,这个时候不也等于点击了ul吗?

因此可以直接把点击事件绑定在ul上

var ul = document.getElementById('ul')
ul.addEventListener('click', function() {})

监听父元素就能监听到子元素?

这是不对的。如果给ul添加个padding。

可以看出,当点击padding部分,也是会触发事件的。原因是我们监听的是ul。

所以这种绑定法,是有bug的,这明显不是我们想要的结果,于是我们可以给事件添加一个判断:

判断一下点击的目标,如果点的是li就触发,不然就不触发。

        ul.addEventListener('click', function(e) {
                    // 检查事件源e.targe是否为Li
                    if (e.target && e.target.nodeName.toUpperCase == "LI") {
                          console.log("点击成功");
                    }
                }

这里就要说到一下用到上面所说的事件传入属性,其中:

  • target:触发该事件时点击的元素

  • currentTarget:触发该事件时监听的元素

那么,还有什么bug吗?有的!

尝试给li加个span试试:

    <ul id="ul">
        <li id="li1"><span>1</span></li>  //给第一个li加一个span
        <li id="li2">2</li>
        <li id="li3">3</li>
        <li id="li4">4</li>
    </ul>

此时发现点击第一个li已经不触发绑定事件了。

虽然前面说道,监听父元素就能达到监听子元素的目的。

但是我们为了修复 ‘padding’ 的bug,添加了一个标签判断,导致 ‘span’ 不会触发绑定事件

if (e.target && e.target.nodeName.toUpperCase == "LI") 

我们必须考虑,li是否还有后代元素。这时候我们就应该先判断点击的元素的祖先元素当中有没有li,如果有li,那点击的还是li。

所以,最后写出的事件委托函数优化如下

ul.addEventListener('click', function() {
                    let el = e.target  \\获得实际点击的元素
                    \\以下while判定点击元素el的祖先元素是否有Li
                    while (el && !el.matches(selector)) {
                        el = el.parentNode
                        if (element === el) {
                            el = null
                        }
                    }
                    if (el) {
                        console.log('执行回调函数')
                    }
                }

事件委托的优点

  1. 节约监听数量(内存)
  2. 可以监听动态生成的元素