什么是事件委托
事件委托也叫事件代理,代理一词很好理解了。就是把自己的事情委托给另一个人去做,打个比方,比如拿快递,像在学校中一般快递都是寄到学校的各个快递点的,学生下课后到指定的地点进行取件,我们把收快递的这个事件委托给快递点,这就是一个事件委托,当然了如果能够送到宿舍最好了O(∩_∩)O哈哈~
👉
阿勇的博客
👈
应用场景一:不改变 DOM 结构
功能需求
事件委托的出现肯定是有他的道理的,举个例子,如果我们想要实现这么一个需求:
- 在页面上创建一个 ul 列表。
- 在 ul 列表下有 5 个 li标签。
- 给每个 li 标签一个文本内容。
- 给 li 一个点击事件,当触发后 li 标签的背景颜色变为红色。
这里简单用代码实现一下:
-
html 部分:
ul>li{这是第$个 li 标签}*5
<ul> <li>这是第1个 li 标签</li> <li>这是第2个 li 标签</li> <li>这是第3个 li 标签</li> <li>这是第4个 li 标签</li> <li>这是第5个 li 标签</li> </ul>
-
js 部分
// 获取所有的 li 标签 let lis = document.getElementsByTagName('li'); // 1. 遍历 lis 给每一个 li 添加点击事件改变背景颜色 for (let item of lis) { item.addEventListener("click", () => { item.style.background = "red"; }); }
- addEventListener 第三个参数可以控制事件冒泡和事件捕获(onclick不可以),默认为 false (冒泡), true (捕获)
- 事件处理 this与onclick一样
- 可以为某个元素绑定多个事件而不会覆盖之前绑定的处理程序 (按照顺序执行,onclick不可以)
功能已经实现,但是设想一下如果有 10000 个li标签,那么我们就需要遍历 10000 次,给每一个 li 添加点击事件,每一个函数就是一个对象,每个对象都会占用内存,对象越多,内存消耗就越大,所表现出来的网站性能就越差劲。所以我们应该减少 DOM 操作。所以事件委托派上用场了。
使用事件委托
使用代码实现一下:
-
html 部分和功能需求中相同,我们主要编写 js 部分代码
-
js 部分
// 获取 ul 标签 let ul = document.querySelector('ul'); // 将单击事件委托给 li 的父级 ul ul.addEventListener('click',(e)=>{ // 兼容性 获取事件源 let ev = e || window.event; let target = ev.target || ev.srcElement; // 进行大小写转换 ==比较 if(target.nodeName.toLowerCase() == 'li'){ target.style.background = 'red'; } })
这是我理解的事件委托使用到的一个场景。
我们也可以通过 id 去完成事件委托
-
html部分
<ul id="myUl"> <li id="insert">insert</li> <li id="delete">delete</li> <li id="update">update</li> <li id="select">select</li> </ul>
-
js 部分
// 获取 ul 标签 let ul = document.querySelector('ul'); // 将单击事件委托给 li 的父级 ul ul.addEventListener('click',(e)=>{ // 兼容性 获取事件源 let ev = e || window.event; let target = ev.target || ev.srcElement; // 通过 switch 判断是点击了哪一个 li switch (target.id) { case "insert": target.innerHTML = target.innerHTML + ":添加一个文本"; break; case "delete": target.innerHTML = ""; break; case "update": target.innerHTML = '这是修改后的文本'; break; case "select": alert('这是查询的文本'); break; } })
应用场景二:改变 DOM 结构
功能需求
- 在页面上创建一个 button 按钮。
- 在页面上创建一个 ul 列表。
- 在 ul 列表下有 5 个 li标签。
- 给每个 li 标签一个文本内容。
- 给 button 一个点击事件,当触发后生成一个 li 插入到 ul 下的最后一个 li 后边。
- 给 ul 下的 li 一个点击事件,当触发后 li 标签的背景颜色变为红色。
这里简单用代码实现一下:
-
html 部分:
ul>li{这是第$个 li 标签}*5
<button>添加新标签</button> <ul> <li>这是第1个 li 标签</li> <li>这是第2个 li 标签</li> <li>这是第3个 li 标签</li> <li>这是第4个 li 标签</li> <li>这是第5个 li 标签</li> </ul>
-
js 部分
// 获取 ul 标签 let ul = document.querySelector("ul"); // 获取 button 按钮 let btn = document.querySelector("button"); btn.addEventListener("click", () => { // 获取 lis 的长度 let leg = lis.length; // 1. 创建一个 li 标签 let li = document.createElement("li"); // 2. 给创建的 li 标签添加文本内容 li.textContent = `这是第${leg + 1}个 li 标签`; // 3. 把创建的 li 标签挂载到 document 中 ul.appendChild(li); }); // 获取 li 标签 let lis = document.getElementsByTagName("li"); for (let item of lis) { item.addEventListener("click", () => { item.style.background = "red"; }); }
存在的问题:
清楚的发现,已经在 DOM 结构中生成的 li 点击后可以改变背景颜色,但是通过 button 按钮插入的 li 当点击后没有效果,这里我理解为,button 的点击事件并没有事件复制过去。因为在程序一加载的时候,for循环已经遍历完成。
使用事件委托
事件委托就不用担心事件复制不了的问题了,使用代码实现一下:
-
html 部分和功能需求中相同,我们主要编写 js 部分代码
-
js 部分
// 获取 ul 标签 let ul = document.querySelector("ul"); ul.addEventListener("click", e => { let ev = e || window.event; let target = ev.target || ev.srcElement; if (target.nodeName.toLowerCase() == "li") { target.style.background = "red"; } }); // 获取 button 按钮 let btn = document.querySelector("button"); btn.addEventListener("click", () => { // 获取 ul 下 li 的长度 let leg = ul.children.length; console.log(leg); // 1. 创建一个 li 标签 let li = document.createElement("li"); // 2. 给创建的 li 标签添加文本内容 li.textContent = `这是第${leg + 1}个 li 标签`; // 3. 把创建的 li 标签挂载到 document 中 ul.appendChild(li); });
其他应用场景:像 Element-ui 中 Dialog 效果可以使用到事件委托,有时间封装一下 Dialog 组件。
事件委托的优缺点
优点
- 减少内存消耗,减少事件绑定。
- 给动态添加的元素绑定事件。
缺点
- 部分事件如 focus、blur 等无冒泡机制,所以无法委托。
- 事件委托有对子元素的查找过程,委托层级过深,可能会被某一层阻止掉。
- 频繁触发的事件,不适合事件委托。
总结
事件委托固然方便,但是我们也要在合适的场景使用它,以免事倍功半。