JS - Event Flow(事件流)
一、基础知识点
事件流概念
当有人通过某种方式(click、mouse、scroll、touch、focus、blur)操作了页面时所引发的处理该次操作的全过程
事件流全过程
捕获阶段
->执行阶段
->冒泡阶段
捕获阶段
:从根节点一直到触发事件的最底层DOM节点(target)的逐层向下遍历过程;执行阶段
:根据红宝书第四版,通常执行阶段
会被理解为是冒泡阶段
的一部分,具体指在冒泡过程中会逐个执行各个元素上的事件处理函数的过程。在后续讲解Event对象的章节中会提到Event对象的eventPhase只读属性可以表示当前执行是否为执行阶段
(eventPhase === 2);冒泡阶段
:和捕获阶段
刚好相反,从触发事件的最底层DOM节点(target)到document(IE5.5及以前的版本会跳过html直达document,现在的浏览器一般是window)的遍历过程;- 下面用html举个小🌰:
<html>
<body>
<div onclick="console.log('div clicked')">
<button onclick="console.log('button clicked')" />
</div>
</body>
</html>
如上有树形结构 (window ->) document -> html -> body -> div -> button
现点击一下button按钮,会引发如下事件流
1. 捕获阶段:(window ->) document -> html -> body -> div -> button;
2. 冒泡阶段:button -> div -> body -> html -> document (-> window);
3. 执行阶段:在冒泡阶段先发现button身上有个onclick事件处理函数就直接执
行,然后冒泡到div发现它身上也有个onclick事件处理函数,于是也执行;
执行结果:
// 'button clicked'
// 'div clicked'
事件流前世今生
Long long time ago,浏览器巨头 网景(Netscape) 和 IE 分别提出了自己对于事件的处理方法,网景(Netscape) 提出捕获流概念,即在向下捕获的时候搞定事件处理,而 IE 提出冒泡流概念,即在向上冒泡的时候搞定事件处理,所以老的IE浏览器只认冒泡不认捕获。后来好大哥W3C(World Wide Web Consortium) 出面把捕获和冒泡做了个合体并生成了现在使用的事件流
二、进阶知识点
绑定事件处理函数的几种方式
1. 通过DOM自身属性绑定
- DOM自身挂载了很多
on${eventName}
属性(如onclick),默认为undefined,当复制为某具体FunctionA后就会在执行阶段
执行; - 因为实际上就是对DOM挂载的属性的重新赋值,因此该方法只能让一个DOM的每个
on${eventName}
属性拥有唯一事件处理函数; - 通过设置属性值的方式绑定的事件处理函数会在
冒泡阶段
执行; - 通过设置属性值的方式绑定所拥有的event对象,
currentTarget
会恒为null - 如果绑定的FunctionA是通过Function声明,则函数this会指向绑定的DOM对象;
- 如果绑定的FunctionA是通过() => {}声明,则函数this会指向箭头函数声明环境;
const $body = document.querySelector('body')
// 1. 箭头函数绑定
$body.onclick = (event) => {
console.log(event) // Event Object
console.log(this) // Window or undefined
}
// 2. Function函数绑定
$body.onclick = function(event) {
console.log(event) // Event Object
console.log(this) // DOM Object
console.log(event.target === this) // true
}
2. 通过attachEvent
(老IE独享方法)绑定
- 因为兼容性极差且已被淘汰,故只做简单介绍;
- 老IE独享,可想而知该方法只支持捕获阶段,具体入参有二:
eventName(String:绑定的事件名,如onclick)
callback(Function:绑定的事件处理函数)
attachEvent
可以给同一DOM同一事件绑定多个事件处理函数,具体的执行顺序与注册顺序相反,后进先出了属于是;
const $body = document.getElementsByTagName('body')
$body.attachEvent('onclick', function() {
console.log('body clicked')
})
3. 通过addEventListener
绑定
- 兼容了捕获流和冒泡流的现行主流绑定方式,具体入参有三:
eventName(String:绑定的事件名,如click)
callback(Function:绑定的事件处理函数)
useCapture(Boolean:是否在捕获阶段执行,default:false)
addEventListener
可以给同一DOM同一事件绑定多个事件处理函数,具体的执行顺序与注册顺序相同(当然第三个参数权重更高,也就是true比false先执行),先进先出了属于是;
const $body = document.getElementsByTagName('body')
$body.addEventListener('click', function() {
console.log('click1')
}, false)
$body.addEventListener('click', function() {
console.log('click2')
})
$body.addEventListener('click', function() {
console.log('click3')
}, true)
/*** click一下之后的输出结果 ***/
// click3
// click1
// click2
解绑事件处理函数的几种方式
1. 解绑DOM自身属性
[DOM].onclick
-> [DOM].onclick = undefined
2. 解绑addEventListener
[DOM].addEventListener('click', clickHandler, useCapture)
-> [DOM].removeEventListener('click', clickHandler, useCapture)
几种绑定方式的优先级(不带attachEvent)
- 优先级的确立基于DOM等级实现,目前共划分了
DOM0
、DOM1
、DOM2
、DOM3
四个等级(可以简单理解为http1.0 、http1.1、http2.0的关系,即优化与新增),每个级别拥有不同水平的DOM事件:DOM0级事件处理
: 只有最远古的通过DOM自身属性绑定;DOM1级事件处理
: 无,因为从DOM0
到DOM1
没做啥子事件处理的事;DOM2级事件处理
: 出来了addEventListener
,支持click等基操;DOM3级事件处理
: 基于DOM2
新增了很多新操作,如focus、input、scroll、keydown、mousedown……
- 事件处理函数的优先级与DOM事件处理等级相反,因此
onclick
执行优先级大于addEventListener
,而又因为addEventListener
注册的事件处理函数执行优先级等同于注册顺序,因此最终优先级为:onclick
>addEventListener[1]
>addEventListener[2]
Event对象
1. Event对象咋来的
- 无论是通过哪种方式绑定的事件处理函数FunctionA,FunctionA都会在执行之前被塞入第一个入参,这个入参就是Event对象
- 经过尝试发现,同一事件在同一个事件流中无论是通过onclick还是通过addEventListener('click') 绑定不同的事件处理函数,最后的函数所接收到的Event对象是同一个,尝试代码如下:
const _html = document.querySelector('body')
const _body = document.querySelector('body')
// 用于暂存先触发的处理函数的Event对象
let _eventCache = null
_html.addEventListener('click', e => {
_eventCache = e
}, true)
_body.onclick = e => {
console.log(e === _eventCache) // true
_eventCache = e
}
_html.addEventListener('click', e => {
console.log(_eventCache === e) // true
})
2. Event对象的能力
- Event对象挂载着和事件有关的所有信息,包括相关DOM、事件类型、事件触发器的数值等等,因此不同事件类型的Event对象所挂载的属性会有差异;
- 虽然存在差异,但是所有对象都会包含如下共有属性和方法(全部是
Readonly
):- type[
String
] -> 触发的事件类型; - bubbles[
Boolean
] -> 当前事件是否可以冒泡; - cancelable[
Boolean
] -> 是否可以取消事件默认行为; - target[
DOM
] -> 当前触发事件流的最底层DOM - currentTarget[
DOM
] -> 绑定该事件处理函数的DOM元素(绑在window的该值为null); - eventPhase[
Number( 0 | 1 | 2 | 3 )
] -> 事件处理函数被执行时刻所处在的事件流阶段:- 0:
PointerEvent.NONE
- 无事发生阶段 - 1:
PointerEvent.CAPTURING_PHASE
- 事件流处于捕获阶段 - 2:
PointerEvent.AT_TARGET
- 事件流处于目标阶段(尝试后发现唯有事件流最底层DOM绑定的事件在执行时eventPhase会返回2) - 3:
PointerEvent.BUBBLING_PHASE
- 事件流处于冒泡阶段
- 0:
- preventDefault[
Function
] -> 取消事件默认行为(比如a标签自动跳转),前提是cancelable为true; - stopPropagation[
Function
] -> 阻止事件继续传播(捕获和冒泡),前提是bubbles为true; - stopImmediatePropagation[
Function
&DOM3
] -> 拥有stopPropagation功能的同时,还能阻止当前元素接下来还未执行的事件处理函数(🌰:body给click事件按序绑定FunctionA、FunctionB、FunctionC,在FunctionB中执行stopImmediatePropagatiom
后,FunctionC将不再执行); - trusted || isTrusted[
Boolean
&DOM3
] -> Event对象是否可信,因为用户可以通过new PointerEvent('click')创建Event对象,所以通过该属性区分是否是事件流自动创建,true为事件流创建、false为用户创建;
- type[
转载自:https://juejin.cn/post/7169816642065530894