create by db on 2021-2-21 14:33:31
Recently revised in 2022-7-20 17:38:36闲时要有吃紧的心思,忙时要有悠闲的趣味
前言
I hear and I fogorget.
I see and I remember.
I do and I understand.
强势插入2022年7月20日
更新当前事件捕获与事件冒泡先后执行顺序为先捕获后冒泡
/强势插入2022年7月20日
作为一名前端开发,DOM是我们最熟悉的伙伴之一——每天F11都能看到它。
但是,你真的懂它吗?能描述一下事件捕获与事件冒泡先后执行顺序吗?
如果一时想不起来,我们就去看一下吧。
正文
DOM 事件流的三个阶段
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM 事件流
当一个 DOM 事件被触发时,它不仅仅只是单纯地在本身对象上触发一次,而是会经历三个不同的阶段:
1. 捕获阶段(Capture Phase):
- 当我们在 DOM 树的某个节点发生了一些操作(例如单击、鼠标移动上去),就会有一个事件发射过去。这个事件从 Window 发出,不断经过下级节点直到触发的目标节点。在到达目标节点之前的过程,就是捕获阶段(Capture Phase)。(所有经过的节点,都会触发这个事件。捕获阶段的任务就是建立这个事件传递路线,以便后面冒泡阶段顺着这条路线返回 Window。)在目标元素对象本身上注册的捕获事件处理程序不会被调用。
2. 目标阶段(Target Phase):
- 当事件不断的传递直到目标节点的时候,最终在目标节点上触发这个事件,就是目标阶段。
3. 冒泡阶段(Bubbling Phase):
- 再从目标事件位置往文档的根节点方向回溯,从内向外冒泡事件对象(我们平时用的事件绑定就是利用的事件冒泡的原理)
如图所示:
事件捕获与事件冒泡先后执行顺序就显而易见了。
DOM 元素绑定 js 事件方式:
onclick
在 html 标签里面或通过赋值的方式创建 onclick
事件 ,重写 onclick
会覆盖之前的属性,只支持冒泡阶段,不存在兼容性问题
绑定事件: element.onclick = function(){}
解绑事件: element.onclick = null
addEventListener
IE8 以下不支持,属于 DOM2 级方法,可以添加多个方法不被覆盖
参数说明:
-
event
,必须。字符串,指定事件名。 不要使用 "on" 前缀。 例如,使用 "click" , 而不是使用 "onclick"。 -
function
必须。指定要事件触发时执行的函数,注意只写函数名,不要带括号。 -
useCapture
可选。布尔值,指定事件是否在捕获或冒泡阶段执行。
绑定事件:
element.addEventListener(
'click',
function(e) {
e.preventDefault() //阻止默认事件
},
false
)
解绑事件:
element.removeEventListener('click',function(){},false)
attachEvent
IE 特有,兼容 IE8 及以下版本,可添加多个事件处理程序,只支持冒泡阶段
参数说明:
event
,必须。字符串,指定事件名。注意加上事件前边的“on”,比如“onclick”和“onmouseover”,这是与addEventListener
的区别。function
要绑定的事件监听函数,注意只写函数名,不要带括号。
绑定事件:
element.attachEvent('onclick', function(e) {
e.returnValue = false //阻止默认事件
})
解绑事件:
element.detachEvent("onclick",function(){})
上代码,做实验
打开在线编辑器 ---> jsbin.com/cedorat/edi…
代码如下:
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
#outer {
text-align: center;
width: 400px;
height: 400px;
background-color: yellow;
margin: 0 auto;
}
#middle {
width: 250px;
height: 250px;
background-color: red;
margin: 0 auto;
}
#inner {
width: 100px;
height: 100px;
background-color: green;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='outer'>
<span>outer</span>
<div id='middle'>
<span>middle</span>
<div id='inner'>
<span>inner</span>
</div>
</div>
</div>
<script>
// 先全部绑定捕获事件再全部绑定冒泡事件
on("outer", "click", o_click_c, true);
on("middle", "click", m_click_c, true);
on("inner", "click", i_click_c, true);
on("outer", "click", o_click_b, false);
on("middle", "click", m_click_b, false);
on("inner", "click", i_click_b, false);
// // 先全部绑定冒泡事件再全部绑定捕获事件
// on("outer", "click", o_click_b, false);
// on("middle", "click", m_click_b, false);
// on("inner", "click", i_click_b, false);
// on("outer", "click", o_click_c, true);
// on("middle", "click", m_click_c, true);
// on("inner", "click", i_click_c, true);
// 挨个绑定捕获及冒泡事件
// on("outer", "click", o_click_c, true);
// on("outer", "click", o_click_b, false);
// on("middle", "click", m_click_c, true);
// on("middle", "click", m_click_b, false);
// on("inner", "click", i_click_c, true);
// on("inner", "click", i_click_b, false);
// 挨个绑定冒泡及捕获事件
// on("outer", "click", o_click_b, false);
// on("outer", "click", o_click_c, true);
// on("middle", "click", m_click_b, false);
// on("middle", "click", m_click_c, true);
// on("inner", "click", i_click_b, false);
// on("inner", "click", i_click_c, true);
// 选择相应元素
function $(element) {
return document.getElementById(element);
}
// 绑定方法
function on(element, event_name, handler, use_capture) {
if (addEventListener) { //所有主流浏览器,除了 IE 8 及更早 IE版本
$(element).addEventListener(event_name, handler, use_capture);
} else { // IE 8 及更早 IE 版本
$(element).attachEvent('on' + event_name, handler);
}
}
function o_click_c() {
console.log("outer_捕获");
}
function m_click_c() {
console.log("middle_捕获")
}
function i_click_c() {
console.log("inner_捕获")
}
function o_click_b() {
console.log("outer_冒泡")
}
function m_click_b() {
console.log("middle_冒泡")
}
function i_click_b() {
console.log("inner_冒泡")
}
</script>
</body>
</body>
</html>
在上述代码中,我们定义了四种执行顺序:
-
先全部绑定捕获事件再全部绑定冒泡事件
-
先全部绑定冒泡事件再全部绑定捕获事件
-
挨个绑定捕获及冒泡事件
-
挨个绑定冒泡及捕获事件
一、先全部绑定捕获事件再全部绑定冒泡事件
我们先运行第一种:
- 点击 outer,打印结果:
"outer_捕获"
"outer_冒泡"
- 点击 middle,打印结果:
"outer_捕获"
"middle_捕获"
"middle_冒泡"
"outer_冒泡"
- 点击 inner,打印结果:
"outer_捕获"
"middle_捕获"
"inner_捕获"
"inner_冒泡"
"middle_冒泡"
"outer_冒泡"
结论:
- 先由外向内事件捕获,一直到事发元素,再由内向外冒泡到根节点上
二、先全部绑定冒泡事件再全部绑定捕获事件
我们先运行第二种:
- 点击 outer,打印结果:
"outer_捕获"
"outer_冒泡"
- 点击 middle,打印结果:
"outer_捕获"
"middle_捕获"
"middle_冒泡"
"outer_冒泡"
- 点击 inner,打印结果:
"outer_捕获"
"middle_捕获"
"inner_捕获"
"inner_冒泡"
"middle_冒泡"
"outer_冒泡"
结论:
- 先由外向内事件捕获,一直到事发元素,再由内向外冒泡到根节点上
三、挨个绑定捕获及冒泡事件
我们先运行第三种:
- 点击 outer,打印结果:
"outer_捕获"
"outer_冒泡"
- 点击 middle,打印结果:
"outer_捕获"
"middle_捕获"
"middle_冒泡"
"outer_冒泡"
- 点击 inner,打印结果:
"outer_捕获"
"middle_捕获"
"inner_捕获"
"inner_冒泡"
"middle_冒泡"
"outer_冒泡"
结论:
- 先由外向内事件捕获,一直到事发元素,再由内向外冒泡到根节点上
四、挨个绑定冒泡及捕获事件
我们先运行第四种:
- 点击 outer,打印结果:
"outer_捕获"
"outer_冒泡"
- 点击 middle,打印结果:
"outer_捕获"
"middle_捕获"
"middle_冒泡"
"outer_冒泡"
- 点击 inner,打印结果:
"outer_捕获"
"middle_捕获"
"inner_捕获"
"inner_冒泡"
"middle_冒泡"
"outer_冒泡"
结论:
- 先由外向内事件捕获,一直到事发元素,再由内向外冒泡到根节点上
阻止事件的传播
如果希望事件到某个节点为止,不再传播,可以使用事件对象的 event.stopPropagation()
方法。
注意:它不是阻止冒泡,而是阻止的事件的传播!!!事件的捕获和冒泡都会阻止掉!!!
// 阻止所有middle捕获之后的事件
document.getElementById("middle").addEventListener('click', function(e) {
e.stopPropagation();
console.log('阻止所有middle捕获之后的事件')
}, true);
点击 inner,打印结果:
outer_捕获
middle_捕获
阻止所有middle捕获之后的事件
只阻止冒泡
如果只希望防止事件冒泡,我们可以借助 addEventListener
的第三个参数,指定事件在冒泡阶段执行。
// 阻止所有middle冒泡之后的事件
document.getElementById("middle").addEventListener('click', function(e) {
e.stopPropagation();
console.log('阻止所有middle冒泡之后的事件')
}, true);
点击 inner,打印结果:
outer_捕获
middle_捕获
inner_捕获
inner_冒泡
middle_冒泡
test.html:89 阻止所有middle捕获之后的事件
注意
stopPropagation
会阻止事件向上层元素冒泡。如果同一个元素绑定了多个事件(addEventListener),那么不会阻止其他事件的执行。
阻止剩余事件
event.stopImmediatePropagation()
方法阻止剩下的事件处理程序被执行。该方法阻止事件在 DOM 树中向上冒泡。停止当前节点,和所有后续节点的事件处理程序的运行。
// 阻止所有middle捕获之后的事件
document.getElementById("middle").addEventListener('click', function(e) {
e.stopPropagation();
// e.stopImmediatePropagation();
console.log('阻止所有middle捕获之后的事件')
}, true);
// 绑定第二个事件
document.getElementById("middle").addEventListener('click', function(e) {
console.log('绑定第二个捕获事件')
}, true);
// 绑定第二个事件
document.getElementById("middle").addEventListener('click', function(e) {
console.log('绑定第三个捕获事件')
}, true);
点击 inner,打印结果:
outer_捕获
middle_捕获
inner_捕获
inner_冒泡
middle_冒泡
test.html:89 阻止所有middle捕获之后的事件
总结
事件流执行顺序
通过以上代码,我们可以看出,关于事件捕获与事件冒泡先后执行顺序:
-
在捕获阶段,先由外向内执行捕获事件;
-
当事件触发在目标阶段时,先捕获,后冒泡;
-
在冒泡阶段,由内向外冒泡到根节点上。
其他:
-
js 代码只能执行捕获或者冒泡其中一个阶段(要么是捕获要么是冒泡)
-
onclick 和 attachevent(ie)只能得到冒泡阶段
-
实际开发中,我们很少使用事件捕获,我们更关注事件冒泡
-
有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave
-
事件的冒泡有时会带来麻烦,不过是可以被阻止的,方法是:stopPropagation()
-
stopPropagation() 方法:终止事件在传播过程的捕获、目标处理或冒泡阶段进一步传播。调用该方法后,该节点上处理该事件的处理程序将被调用,事件不再被分派到其他节点。
路漫漫其修远兮,与诸君共勉。
参考文档:
后记:Hello 小伙伴们,如果觉得本文还不错,记得点个赞或者给个 star,你们的赞和 star 是我编写更多更丰富文章的动力!GitHub 地址
文档协议
db 的文档库 由 db 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/danygitgit上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。