Trying to understand some people is like trying to pick up turd by the clean end.
事件是javascript一个至关重要的部分,通过派遣/监听事件,可以实现很多动态功能。通过对事件机制的重温,总是能重新刷新一些曾经模糊的知识点。特别写此文给自己加深印象, 如有错误欢迎指正!
以下文章皆以click事件为例:
事件流机制
事件传递过程
事件从 用户行为触发 到 执行完毕 共有三个状态: 捕获(1) - 抵达target元素(2) - 冒泡(3),由事件对象的eventPhase属性标识。看图:
假设用户点击了一个表格的单元格,按照上图序号表明的顺序, 用户点击鼠标的那一刻开始经历三个过程:
从最外层的window到最内层的td元素按顺序一一捕获到click事件。此过程event.eventPhase=1。
如果当前元素绑定了要在 捕获阶段执行 的监听函数(listener), 会在此过程开始执行。 (什么是 捕获阶段执行 的监听函数稍后再说)
事件到达目标元素(可追溯到的最内层元素)。此时event.eventPhase=2。
事件开始向上冒泡。 此时event.eventPhase=3。
如果当前元素绑定了要在 冒泡阶段执行 的监听函数,会在此过程开始执行。
事件对象
说明两个概念: 事件对象(event)的两个属性:event.target 和 event.currentTarget。
event.target 是最终点击到的DOM元素, 在整个事件传递过程中不会变化。
event.currentTarget 是传递过程中,当前遍历到的元素。
`
<tbody> <tr> <td> table cell </td> </tr> </tbody>
`如上代码(撇开body及以上的对象): 点击table cell区域,
currentTarget
依次是table > tbody > tr > td(eventPhase=2) > tr > tbody > table
,而target
一直是td
绑定与监听
举个例子来说明事件的绑定和监听,方便理解上面的概念。
//example.html
<div id="d1">
<div id="d2">
<div id="d3">
<div id="d4"></div>
</div>
</div>
</div>
// style.css
#d1 {
width: 200px;
height: 200px;
background: aliceblue;
margin: auto;
text-align: center;
}
#d2 {
width: 150px;
height: 150px;
background: antiquewhite;
}
#d3 {
height: 100px;
width: 100px;
background: beige;
}
#d4 {
width: 50px;
height: 50px;
background: azure;
}
//example.js
for(let elem of document.querySelectorAll("#d1, #d1 *")) {
elem.addEventListener("click", e => console.log(`Capturing: ${elem.id}`), true);
elem.addEventListener("click", e => console.log(`Bubbling: ${elem.id}`));
}
//output (点击d4区域)
Capturing: d1
Capturing: d2
Capturing: d3
Capturing: d4
Bubbling: d4
Bubbling: d3
Bubbling: d2
Bubbling: d1
绑定事件
有些js库封装了自己都事件绑定函数,这里不赘述,本质都是基于js原生都addEventListener方法。
addEventListener接收3个函数,前两个分别是事件类型和监听函数,不必多说。 注意第三个参数: 可以是Object / Boolean.
Boolean
true - 表示指定都监听函数将在事件 捕获阶段 执行
false(Default) - 表示监听函数在事件 冒泡阶段 执行这个值直接影响到不同元素对同一事件响应的作出先后顺序
Object
{ capture: Boolean, once: Boolean, passive: Boolean }
这种形式不是本文的重点,详情可参考这里!
target
在整个事件传递过程中,
如果用户点击d4区域:
- currentTarget 的变化:
d1
>d2
>d3
>d4
>d4
>d3
>d2
>d1
。 target:
d4
如果用户点击d2中不覆盖d3,d4的区域:
currentTarget 的变化:
d1
>d2
>d2
>d1
。- target:
d2
也就是说: event.target的值取决于事件触发时可追溯到的最内层元素。 点击d2中不覆盖d3,d4的区域,d2就是当前点击事件的最内层元素,所以event.target指向d2。
细枝末节
event对象有两个方法 preventDefault
和 stopPropergation
:
event.preventDefault() - 取消事件的默认行为;
event.stopPropergation() - 阻止事件继续传递;
其中, preventDefault
是取消事件的默认行为,但并不会阻止事件的传递,会继续 捕获 -> 抵到 -> 冒泡 的过程。 如下例:
<input id="checkbox" type='checkbox' value=true />
// js
var el = document.getElementById('checkbox');
el.addEventListener(‘click’, e => e.preventDefault());
点击这个checkbox的时候,正常情况checkbox应该被勾上, 但调用了preventDefault()
方法,取消了该DOM元素点击事件的默认行为,checkbox就不会再勾选上。
stopPropergation
是阻止事件继续往后传递。例如,
<input id="checkbox" type='checkbox' value=true />
// js
var el = document.getElementById('checkbox');
el.addEventListener('click', e => e.stopPropergation(), true);
el.addEventListener('click', e => console.log(el.tagName), true);
点击这个checkbox,由于在 捕获阶段 调用了stopPropergation()
, 在此被截断,阻止了事件继续向后传递,根本不会有后面冒泡的过程,所以最后根本不会输出元素标签。通常元素的默认点击行为是在事件冒泡阶段才执行,所以这里虽然点击了checkbox,但仍然不会勾选上。
总结
事件传递的三个状态: 捕获(1)、抵达目标(2)、冒泡(3),由事件对象Event的eventPhase属性标识;
Event.target始终指向事件触发时可追溯到的最深层元素;Event.currentTarget指事件传递过程中当前遍历到的元素;
Event.preventDefault() 取消事件的默认行为,但不会阻止传递; Event.stopPropergation()阻止事件继续向后传递;