事件流
概述
触发事件时,事件流从文档根元素流向触发事件的目标元素,然后再从目标元素流向根元素。前一阶段叫事件捕获,后一阶段叫事件叫事件冒泡。在事件流流动的时候相应的事件处理回调会被执行。
阻止事件流(stopPagation & stopImmediatePropagation)
使用 stopPagation
和 stopImmediatePropagation
可以阻止事件流的流动。
事件流的方向总是从根元素到目标元素然后再到根元素,这个流程是固定的,所以在其中的一个节点调用这个 API 就能中断事件流。
但是 stopPagation
和 stopImmediatePropagation
不同,stopImmediatePropagation
会阻止事件流,并且立马停止所有事件监听的回调函数,包括在当前元素(当前元素的一个监听回调调用了 stopImmediatePropagation)的其他监听回调。
而 stopPagation
会阻止事件流,但是仍然会把当前元素的其他监听回调执行完毕。
两者相同的是都会阻止事件流继续流动,不管是在捕获阶段还是冒泡阶段。
并不是所有的事件都有完整的事件流
一些特殊的事件如 load
、error
、scroll
等,它们通常与特定的元素或文档本身关联,但它们不涉及捕获或冒泡阶段。这些事件在特定条件下触发,并且与事件流的传播无关。
没有捕获阶段的事件:
- 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) {
//
}
//
});