事件委托(代理)
什么是事件?
事件是我们在编程时系统内发生的动作或者发生的事情(譬如点击鼠标、敲击键盘)。事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。事件有多种类型,以下代码我们以“鼠标点击”事件为例。
浏览器的事件模型,就是通过监听函数(listener)监听事件,在事件发生改变后作出反应。事件的「默认行为」是指浏览器规定的行为,例如单击超链接会跳转…
事件的传播
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段:
- 事件捕获阶段(capture phase):从顶级元素往目标节点传递
- 事件目标处理阶段(target phase): 到达目标节点
- 事件冒泡阶段(bubbling phase): 从目标节点往顶层元素传递
监听事件
JavaScript 有三种方法为事件绑定监听函数:
-
1.HTML 的 on- 属性
- 元素的事件监听属性,都是 on 加上事件名,比如 onload 就是 on + load,表示 load 事件的监听代码。
- HTML 语言允许在元素的属性中,直接定义某些事件的监听代码:。这些属性的值是将会执行的代码(所以函数不要忘记加括号())
- 使用这个方法指定的监听代码,只会在冒泡阶段触发。
-
2.元素节点的事件属性
- div.onclick = function (event) { console.log(‘1’)}
- 使用这个方法指定的监听函数,也是只会在冒泡阶段触发。
-
3.EventTarget.addEventListener()
- window.addEventListener(‘load’, doSomething, false)
总结:
![](/qrcode.jpg)
“HTML 的 on- 属性”:违反了 HTML 与 JavaScript 代码相分离的原则,不推荐使用。
“元素节点的事件属性”:同一个事件只能定义一个监听函数,如果定义两次 onclick 属性,后一次定义会覆盖前一次。不推荐使用。
EventTarget.addEventListener() 推荐使用,优点:
- 同一个事件可以添加多个监听函数。
- 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数。
- DOM 节点之外的其他对象(比如 window、XMLHttpRequest 等)也有这个接口,它是整个 JavaScript 统一的监听函数接口。
如何阻止事件冒泡和默认行为:
- event.stopPropagation()
- event.preventDefault()
事件委托(事件代理)
概述:根据捕获与冒泡,如果我们有许多以类似方式处理的元素,那么就不必为每个元素都分配一个事件处理程序 —— 而是将单个处理程序放在它们的共同祖先上。
优点:
- 减少内存的使用(减少函数的使用)
- 可以监听动态元素
例子如下:
<ul id="myLink">
<li id="1">aaa</li>
<li id="2">bbb</li>
<li id="3">ccc</li>
</ul>
我们若要给其中的每个
- 标签都加上一个点击事件。
-
简易版:
ul.addEventListener('click', function(e){ if(e.target.tagName.toLowerCase() === 'li'){ fn() // 执行某个函数 } })
高级版:
function delegate(element, eventType, selector, fn) { element.addEventListener(eventType, e => { let el = e.target while (!el.matches(selector)) { if (element === el) { el = null break } el = el.parentNode } el && fn.call(el, e, el) }) return element }
拓展
什么是事件委托?
当一个循环达到几十次或者上百次,并且在其本身绑定事件,渲染的时候,给每个dom绑定事件,这样操作是很耗费性能的,这时我们就应该利用冒泡的机制和事件流的特性去把他的事件绑定在父亲本身,这样我们只需要绑定一个事件就能操作所有元素拉。
举个通俗点的例子蛤
某个班级100个人,每个人刚好都有快递要拿,如果每个人都出去拿自己的快递的话,数量是不是非常大?这时班长要去拿快递啦,其他同学就可以委托班长给班长去拿,然后再由班长根据快递上面的名字分发到每个人同学手上,这就是事件委托啦。
好了,接下来让代码去描述上面的代码吧(技术栈为Vue哦)html代码内容
p 标签上的data-obj 是我自定义的属性,可以根据dom对象上的dataset拿到数据,格式:data-自定义名字。
看ul标签,点击事件我只绑定在父元素身上哦。
刚开始的时候界面长这样
最最最最重要的事件逻辑的来啦
先获取当前的事件对象,然后判断当前点击的节点是否是ul
我随便点一个
由此可见事件委托是成功的,我们来看看点击时的事件对象结构
可以很清楚的看清 我们绑定的值和当前点击的时哪个元素。
我写项目的时候也很经常使用事件委托去处理多个相同的事件,这也是优化性能的一种方式(少了遍历所有 li 节点的操作,性能上肯定更加优化)
例子源码
<template> <div id="app"> <ul @click="giveDelivery"> <li v-for="c in 10" :key="c" :data-obj="c"> 同学 { { c }}</li> </ul> <p> 同学 { { val }} 的快递 </p> </div> </template> <script> export default { name: 'App', data() { return { val: '' } }, methods: { giveDelivery(event) { const e = event || window.event // 获取点击的事件对象 console.log(e) if(e.target.nodeName.toLowerCase() === 'li') { this.val = e.target.dataset.obj // 拿到绑定 data-obj 的值 } } } } </script> <style> ul { display: flex; } li { width: 80px; line-height: 30px; margin-right: 5px; cursor: pointer; background-color: red; color: #fff; list-style: none; text-align: center; } p { width: 500px; line-height: 300px; color: #fff; font-size: 24px; font-weight: bold; text-align: center; background-color: #000; } </style>