在JavaScript中,事件委托Event delegation
是一种事件的响应机制,当需要监听不存在的元素或是动态生成的元素时,可以考虑事件委托。
事件委托得益于事件冒泡(有关事件冒泡可以参考事件冒泡与事件捕获),当监听子元素时,事件冒泡会通过目标元素向上传递到父级,直到document
,如果子元素不确定或者动态生成,可以通过监听父元素来取代监听子元素。
举个例子:
假设页面从存在一个ul
,其子元素动态生成.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>事件委托</title> </head> <body> <button>点击</button> <ol></ol> <script> let btn = document.getElementsByTagName('button')[0] let ol = document.getElementsByTagName('ol')[0] btn.addEventListener('click',function(){ let li = document.createElement('li') let number = parseInt(Math.random() * 100, 10) li.textContent=number ol.appendChild(li) }) <script> </body> </html>
|
当点击按钮时,ol
列表中动态创建li
,如果给li
添加click
事件,当li
被点击时,li
本身被删除,那么可以这样实现
1 2 3 4 5 6 7 8 9
| btn.addEventListener('click',function(){ let li = document.createElement('li') let number = parseInt(Math.random() * 100, 10) li.textContent=number ol.appendChild(li) li.addEventListener('click',function(){ li.remove(); }) })
|
但是这样就会使得给每个li
都添加了click
事件(或者其他的事件),而页面中的li
是不确定有多少的,如果页面中生成很多的li
,那么就会造成内存占用过多,因此我们可以使用事件委托来解决这个问题。
1 2 3 4 5 6 7 8 9 10 11
| /* li.addEventListener('click',function(){ li.remove(); }) */ ol.addEventListener('click',function(e){ let ele = e.target if(ele.tagName==='LI'){ ele.remove() } })
|
在这个例子中,通过给父级而非子集添加事件,当事件被抛到更上层的父节点的时候,我们通过检查事件的目标对象target
来判断并获取事件源li
,当子节点被点击的时候,click
事件会从子节点开始向上冒泡。父节点捕获到事件之后,通过判断e.target.nodeName
来判断是否为我们需要处理的节点。并且通过e.target
拿到了被点击的Li
节点。从而可以获取到相应的信息,并作处理。
但是当li
中有其他的标签时如span
标签(或是其他的标签),直接使用上述方法就会导致事件无法被正确触发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| btn.addEventListener('click',function(){ let li = document.createElement('li') let span = document.createElement('span') let number = parseInt(Math.random() * 100, 10) span.textContent=number li.appendChild(span) ol.appendChild(li) ol.addEventListener('click',function(e){ let ele = e.target if(ele.tagName==='LI'){ ele.remove() } }) })
|
此时,当点击span
时,li
并未被删除,点击span
与li
之间的部分li
才会被删除,试想当子元素li
中有多层嵌套时,就会导致事件无法被正确触发,因此我们需要对li
进行判断,判断点击的是否是ol
下的li
,否则就继续查找,直到查找到li
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| btn.addEventListener('click',function(){ let li = document.createElement('li') let span = document.createElement('span') let number = parseInt(Math.random() * 100, 10) span.textContent=number li.appendChild(span) ol.appendChild(li) ol.addEventListener('click',function(e){ let ele = e.target while(ele.tagName!=='LI'){ if(ele===ol){ ele=null; break; } ele=ele.parentNode } ele && ele.remove() }) })
|
此时不论li
嵌套多少层,li
的click
事件都会被正确触发。
事件委托可以通过监听父级来达到监听子级的效果,减少了监听器的数量,使用内存也相对减少。当在一些难以追踪的情况下,从DOM从删除的元素仍保留「记忆」(即泄露),这些泄露的「记忆」通常与事件绑定联系在一起,使用事件委托,可以随意的对子元素进行事件绑定而不用担心忘记解绑它们的监听器。
参考:
- What is DOM Event delegation?
- How JavaScript Event Delegation Works