JS:事件委托

基本概念

把想要被监听的元素委托给它的父元素或祖先元素来监听。

实例分析

假如有个button标签,当用户每次点击时,会在有序列表‘ol’里添加一个列表‘li’,给每个‘li’绑定上监听事件。当用户点击某一‘li’时,会执行某种操作,这里我们假定绑定的监听事件是把’li‘标签删除。
HTML代码如下:

1
2
<button id="takenumber">+</button>
<ol id="numbers"></ol>

JS代码如下:

1
2
3
4
5
6
7
8
9
10
takenumber.addEventListener('click',function(e){
let number = Math.round(Math.random()*100)
let li = document.createElement('li')
li.textContent = number
//把每个动态生成的li绑定监听函数
li.addEventListener('click',function(e){
li.remove()}
numbers.appendChild(li)
}
}


上面代码是对每个动态生成的’li‘都进行了监听,然而这种监听会存在一个缺点,假如我生成一万个’li‘标签,岂不是要有1万个监听函数。这极大地浪费了内存。
此时,如果我们用事件委托的话就不会存在这种问题。

事件委托

  • 监听还不存在的元素
  • 减少监听器个数(用代码来换内存)
    JavaScript代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    takenumber.addEventListener('click',function(e){
    let number = Math.round(Math.random()*100)
    let li = document.createElement('li')
    li.textContent = number
    numbers.appendChild(li)
    }
    //监听父元素
    numbers.addEventListener('click',function(e){
    //e.target表示用户点击的元素
    let elment = e.target
    if(element.tagName === 'LI'){
    element.remove()
    }
    }


通过以上的代码即可实现事件委托,然而这是一个有bug的事件委托。假如生成的’li‘标签里还有子元素’span‘,当用户点击的元素不是’li‘,而是’span‘时,根本就不会删除这个’li‘标签。如此,我们需要改善上面的JS代码。

修复事件委托的bug

当用户点击某个元素时,我们判断这个元素的父元素的’tagName‘是否为’LI‘,可以用if…else判断,此时我们还需考虑的一个问题是,假如’li‘标签下嵌套了多层,那就不仅仅是判断父元素了,还得判断其父父元素甚至更多,于是我们用while循环来判断。JS代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
takenumber.addEventListener('click',function(e){
let number = Math.round(Math.random()*100)
let li = document.createElement('li')
li.textContent = number
numbers.appendChild(li)
}
numbers.addEventListener('click',function(e){
let element = e.target
while(element.tagName !== 'LI'){
if(element === numbers){
element = null //无操作
break
}
element = element.parentNode
}
//有假返假,都真返lastone
element && element.remove()
}

混淆点

事件机制不是有三大共识吗?其中有一条为:

child被点击了,意味着parent也被点击了。

点击是不存在冒泡的,并不是说你点了child,就相当于点了parent,它只是一个通知阶段,通过冒泡的形式通知parent,你的子元素child被点击了。e.target始终是你点击的元素,并不存在什么冒泡,冒泡只是一个通知机制。