跳到主要内容

事件流

概述

触发事件时,事件流从文档根元素流向触发事件的目标元素,然后再从目标元素流向根元素。前一阶段叫事件捕获,后一阶段叫事件叫事件冒泡。在事件流流动的时候相应的事件处理回调会被执行。

阻止事件流(stopPagation & stopImmediatePropagation)

使用 stopPagationstopImmediatePropagation 可以阻止事件流的流动。

事件流的方向总是从根元素到目标元素然后再到根元素,这个流程是固定的,所以在其中的一个节点调用这个 API 就能中断事件流。

但是 stopPagationstopImmediatePropagation 不同,stopImmediatePropagation 会阻止事件流,并且立马停止所有事件监听的回调函数,包括在当前元素(当前元素的一个监听回调调用了 stopImmediatePropagation)的其他监听回调。

stopPagation 会阻止事件流,但是仍然会把当前元素的其他监听回调执行完毕。

两者相同的是都会阻止事件流继续流动,不管是在捕获阶段还是冒泡阶段。

并不是所有的事件都有完整的事件流

一些特殊的事件如 loaderrorscroll 等,它们通常与特定的元素或文档本身关联,但它们不涉及捕获或冒泡阶段。这些事件在特定条件下触发,并且与事件流的传播无关。

没有捕获阶段的事件:

  • focus:当元素获得焦点时触发,没有捕获阶段。
  • focusin:当元素获得焦点时触发,没有捕获阶段。与 focus 不同的是,focusin 事件可以冒泡。
  • mouseenter:当鼠标进入元素时触发,没有捕获阶段。
  • mouseover:当鼠标经过元素时触发,有捕获阶段。

没有冒泡阶段的事件:

  • blur:当元素失去焦点时触发,没有冒泡阶段。
  • focusout:当元素失去焦点时触发,没有冒泡阶段。与 blur 不同的是,focusout 事件可以冒泡。
  • mouseleave:当鼠标离开元素时触发,没有冒泡阶段。
  • mouseout:当鼠标离开元素时触发,有冒泡阶段。

preventDefault

preventDefault 会阻止元素触发事件后的默认行为,并不会阻断事件流。

例如, contextmenu 事件,就是点击鼠标右键,浏览器默认的事件处理的弹出一个菜单,但是如果在元素上监听 contextmenu 事件,并且调用 preventDefault 那么浏览器默认弹出的菜单就不会出现。

el.addEventListener('contextmenu', (event) => event.preventDefault());

实验代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.container div {
border: solid 1px black;
padding: 10px;
}
</style>
</head>
<body>
<div class="container">
<div id="parent">
parent
<div id="child">
child1
<div id="child2">child2</div>
</div>
</div>
</div>
<script>
const parent = document.getElementById('parent');
const child = document.getElementById('child');
const child2 = document.getElementById('child2');

parent.addEventListener('click', function (event) {
console.log('Parent bubbling');
});

child.addEventListener('click', function (event) {
console.log('Child bubbling phase');
});

child2.addEventListener('click', function (event) {
console.log('Child2 bubbling phase');
// event.stopImmediatePropagation();
// event.stopPropagation();
});
child2.addEventListener('click', function (event) {
console.log('Child2 bubbling 其他事件处理程序');
});
child2.addEventListener(
'click',
function (event) {
console.log('Child2 capturing 其他事件处理程序');
// event.stopImmediatePropagation();
// event.stopPropagation();
},
true
);

parent.addEventListener(
'click',
function (event) {
console.log('Parent capturing phase');
},
true
);

child.addEventListener(
'click',
function (event) {
console.log('Child capturing phase');
},
true
);

child2.addEventListener(
'click',
function (event) {
console.log('Child2 capturing phase');
},
true
);

// contextmenu

parent.addEventListener('contextmenu', function (event) {
console.log('parent contextmenu bubbling phase');
});

child.addEventListener('contextmenu', function (event) {
console.log('Child contextmenu bubbling phase');
});

child2.addEventListener('contextmenu', function (event) {
console.log('Child2 contextmenu bubbling phase');
// event.preventDefault();
});

parent.addEventListener(
'contextmenu',
function (event) {
console.log('parent contextmenu capturing phase');
},
true
);

child.addEventListener(
'contextmenu',
function (event) {
console.log('Child contextmenu capturing phase');
},
true
);

child2.addEventListener(
'contextmenu',
function (event) {
console.log('Child2 contextmenu capturing phase');
event.preventDefault();
},
true
);
</script>
</body>
</html>

事件委托

基于事件流模型,将事件监听挂载到父级元素,子元素触发事件时统一在父元素的监听中处理,从而减少对子元素监听器的注册。

const parent = document.getElementById('parent');
parent.addEventListener('click', (event) => {
const { target } = event;
if (target === child1) {
//
} else if (target === child2) {
//
}
//
});
网站备案:蜀ICP备2023001425号👏 Powered By Docusaurus, Semi Design