基本概念
事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown…)的函数委托到另一个元素;
一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
举个例子,比如一个宿舍的同学同时快递到了,一种方法就是他们都傻傻地一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一一分发给每个宿舍同学;
事件冒泡
前面提到 DOM 中事件委托的实现是利用事件冒泡的机制,那么事件冒泡是什么?
如上图所示,事件模型是指分为三个阶段:
- 捕获阶段:The capture phase - 事件从最顶层元素 window 一直传递到目标元素的父元素;
- 目标阶段:The target phase - 事件到达目标元素. 如果事件指定不冒泡. 那就会在这里中止.
- 冒泡阶段:The bubble phase - 事件从目标元素父元素向上逐级传递直到最顶层元素 window. 即捕获阶段的反方向.
- 事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;
详解
那这里又有一个新的疑问, 既然捕获和冒泡阶段都会触发事件, 那先捕获再冒泡, 岂不是路径上的元素都会触发两次事件?
在 DOM2 中, 事件监听机制提供了一个参数来决定事件是在捕获阶段生效还是在冒泡阶段生效, 接下来简要学习下 addEventListener
.
addEventListener
EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行
具体使用请移步MDN
先看下参数
-
type: 表示监听事件类型的字符串. 事件列表.
-
listener: 当所监听的事件类型触发时的回调. 会接收到一个事件通知对象.
-
options:
可选
. 可用的选项如下:- capture: Boolean, 如果是 true, 表示 listener 会在捕获阶段触发. 默认是 false. 冒泡阶段触发.
- once: Boolean, 如果是 true, 表示 listener 在添加之后最多只调用一次.
- passive: Boolean, 如果是true, 表示 listener 永远不会调用 preventDefault(). 如果是false, listener 仍然调用了这个函数, 客户端将会忽略它并抛出一个控制台警告.
捕获阶段例子:
我们设置了capture为true,也就意味着在捕获阶段触发事件
<div>
<ul>
<li>
<button>按钮</button>
</li>
</ul>
</div>
<script>
const box = document.querySelector('div');
box.addEventListener('click', () => {
alert('我是div')
}, {
capture: true
})
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
alert('我是按钮')
}, {
capture: true
})
</script>
效果:
点击按钮,会先弹出 我是div ,再弹出 我是按钮 ,可自行测试
冒泡阶段例子:
capture默认为false,意味着在冒泡阶段触发事件
<div>
我是div
<ul>
<li>
<button>按钮</button>
</li>
</ul>
</div>
<script>
const box = document.querySelector('div');
box.addEventListener('click', () => {
alert('我是div')
})
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
alert('我是按钮')
})
</script>
效果:
点击按钮,会先弹出 我是按钮 ,再弹出 我是div
事件委托的优点
1. 减少内存消耗
试想一下,若果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件;
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li
如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;
因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul
上,然后在执行事件的时候再去匹配判断目标元素;
那么我们如何知道是谁触发事件了呢?
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom.
我们看一个🌰:
<ul>
<li>list1</li>
<li>list2</li>
<li>list3</li>
<li>list4</li>
<li>list5</li>
</ul>
<script>
const list = document.querySelector('ul');
list.addEventListener('click', (e) => {
alert('事件触发')
console.log(e.target.innerHTML);
})
</script>
比如我点击list1,那么1、弹出 事件触发,2、控制台输出list1
具体效果请自行测试
2.简化了dom节点更新时,相应事件的更新,比如
- 不用在新添加的li上绑定click事件。
- 当删除某个li时,不用移解绑上面的click事件。
补充:
target/
currentTarget/
relateTarget`具体指向什么目标
- target:事件源
- currentTarget: 事件属性返回其监听器触发事件的节点,即当前处理该事件的元素、文档或窗口。
- relatedTarget 事件属性返回与事件的目标节点相关的节点。
对于 mouseover 事件来说,该属性是鼠标指针移到目标节点上时所离开的那个节点。
对于 mouseout 事件来说,该属性是离开目标时,鼠标指针进入的节点。
对于其他类型的事件来说,这个属性没有用。