前言
JavaScript 事件流是前端开发中的重要概念,作为面试考点之一,它常常被用来考察面试者对事件处理机制的理解和掌握程度。 深入了解事件流对于我们更好地理解事件的触发顺序、决定事件处理程序的执行顺序以及避免意外的事件冲突和重复执行等方面都有很大帮助。
什么是 JavaScript 事件流?
JavaScript 事件流是指在 HTML 文档中,事件的传播过程,即事件从触发元素向上或向下传递的路径。
事件流分为三个阶段:
- 事件捕获阶段(Capturing phase) :在这个阶段中,事件从最外层的元素开始向内部元素进行传播,直到达到触发事件的具体目标元素。
- 目标阶段(Target phase) :当事件传播到达目标元素时,就进入了目标阶段。在这个阶段,事件达到了目标元素并在目标元素上触发相应的事件处理程序。
- 事件冒泡阶段(Bubbling phase) :在目标阶段完成后,事件会沿着相反的路径从目标元素向外部元素进行冒泡传播,直到达到页面的最外层元素。
代码示例
<!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>
#a{
width: 400px;
height: 400px;
background-color: rgb(245, 56, 13);
}
#b{
width: 200px;
height: 200px;
background-color: rgb(27, 140, 238);
}
#c{
width: 100px;
height: 100px;
background: #000;
}
</style>
</head>
<body>
<div id="a">
<div id="b">
<div id="c">
</div>
</div>
</div>
<script>
let a = document.getElementById('a')
let b = document.getElementById('b')
let c = document.getElementById('c')
a.addEventListener('click', () => {
console.log('a被点击');
})
b.addEventListener('click', () => {
console.log('b被点击');
})
c.addEventListener('click', () => {
console.log('c被点击');
})
</script>
</body>
</html>
JavaScript部分
-
事件绑定:使用
document.getElementById
获取id为’a’、'b’和’c’的元素,并为它们分别绑定了点击事件监听器。 -
事件处理程序:
- 当点击id为’a’的div元素时,会在控制台输出’a被点击’。
- 当点击id为’b’的div元素时,会在控制台输出’b被点击,a被点击’。
- 当点击id为’c’的div元素时,会在控制台输出’c被点击,b被点击,a被点击’。
图是点击id为’c’的div元素效果
JavaScript事件流允许开发人员在事件的不同阶段对其进行捕获或者处理,这样可以更加灵活地对不同元素的事件进行响应和处理。
利用 addEventListener 方法来控制事件流向
事件流的默认行为是从目标元素开始,经过捕获阶段后再进入冒泡阶段。
但我们可以使用 addEventListener 方法的第三个参数来控制事件的触发顺序。
- 如果将该参数设置为 true,则事件将在捕获阶段触发。
- 如果设置为 false 或省略该参数,则事件将在冒泡阶段触发。
代码示例
<script>
let a = document.getElementById('a')
let b = document.getElementById('b')
let c = document.getElementById('c')
a.addEventListener('click', () => {
console.log('a被点击');
}, false)
b.addEventListener('click', () => {
console.log('b被点击');
}, true)
c.addEventListener('click', () => {
console.log('c被点击');
}, false)
</script>
解释代码行为
-
a.addEventListener('click', () => { console.log('a被点击'); }, false)
- 当点击id为’a’的div元素时,该事件处理程序将在冒泡阶段执行。
- 输出:‘a被点击’
-
b.addEventListener('click', () => { console.log('b被点击'); }, true)
- 当点击id为’b’的div元素时,该事件处理程序将在捕获阶段执行。
- 输出:‘b被点击 a被点击’
-
c.addEventListener('click', () => { console.log('c被点击'); }, false)
- 当点击id为’c’的div元素时,该事件处理程序将在冒泡阶段执行。
- 输出:‘b被点击 c被点击 a被点击’
需要注意:事件捕获和事件冒泡是DOM事件传播的两个阶段,是互斥的,它们不会同时发生在同一个事件上
“阻止事件冒泡"和"立即停止事件冒泡”
当使用JavaScript处理DOM事件时,event.stopPropagation()
和event.stopImmediatePropagation()
这两个方法可以帮助我们更精细地控制事件的传播。
-
event.stopPropagation()
-
用法:调用
event.stopPropagation()
方法可以阻止事件在DOM树中进一步传播,即阻止事件冒泡。 -
示例用法:
a.addEventListener('click', () => { console.log('a被点击'); }, false) b.addEventListener('click', (event) => { console.log('b被点击'); event.stopPropagation() // 阻止事件冒泡,仅在当前元素上触发事件处理程序 }, false) c.addEventListener('click', (event) => { console.log('c被点击'); }, false)
-
解释代码行为
当点击元素 a
、b
或 c
时,事件处理程序将会按照以下顺序执行:
-
当点击元素
a
时:- 会触发
a
元素上绑定的点击事件处理程序,输出'a被点击'
。
- 会触发
-
当点击元素
b
时:- 会依次触发
b
元素和其父级元素(如果有)上绑定的点击事件处理程序。 - 在
b
元素的点击事件处理程序中,输出'b被点击'
,然后调用event.stopPropagation()
方法阻止事件冒泡。
- 会依次触发
-
当点击元素
c
时:- 首先输出
'c被点击 b被点击'
,然后调用event.stopPropagation()
方法阻止事件冒泡。
- 首先输出
-
event.stopImmediatePropagation()
-
用法:调用
event.stopImmediatePropagation()
方法会立即停止事件在DOM树中的传播,并阻止同类型的其他事件处理程序被调用。 -
示例用法:
a.addEventListener('click', () => { console.log('a被点击'); }, false) b.addEventListener('click', () => { console.log('b被点击'); }, false) c.addEventListener('click', (event) => { console.log('c被点击'); event.stopImmediatePropagation() }, false) c.addEventListener('click', () => { console.log('c被点击2'); })
-
解释代码行为
当点击元素 a
、b
或 c
时,事件处理程序将会按照以下顺序执行:
-
当点击元素
a
时:- 会触发
a
元素上绑定的点击事件处理程序,输出'a被点击'
。
- 会触发
-
当点击元素
b
时:- 会触发
b
元素上绑定的点击事件处理程序,输出'b被点击 a被点击'
。
- 会触发
-
当点击元素
c
时:- 会依次触发
c
元素上绑定的两个点击事件处理程序。 - 在第一个点击事件处理程序中,首先输出
'c被点击'
,然后调用event.stopImmediatePropagation()
方法阻止事件传播,并且不会继续触发其他相同类型的事件处理程序。 - 因此,第二个点击事件处理程序不会被执行,不会输出
'c被点击2'
。
- 会依次触发
在使用这两种方法时,需要注意以下几点:
- 这两个方法通常在事件处理程序中调用,用于控制事件的传播行为。
event.stopPropagation()
只能阻止事件继续向上冒泡,但不会影响其他事件处理程序的执行。而event.stopImmediatePropagation()
会立即停止事件传播,并阻止同类型的其他事件处理程序被调用。- 如果有多个事件处理程序绑定在同一个元素上,并且其中一个调用了
event.stopImmediatePropagation()
,则后续的事件处理程序将不再被执行。
事件代理(事件委托)
事件代理(也称为事件委托)是一种常用的前端开发技术,通过将事件处理程序绑定到父元素而不是每个子元素上,来管理事件。当子元素触发事件时,事件会冒泡到父元素,然后在父元素上触发事件处理程序。这样做的好处包括:
- 性能优化:减少了添加事件处理程序的次数,节省了内存和提高了性能。特别是在需要管理大量子元素时,使用事件代理可以显著减少内存消耗和页面加载时间。
- 动态元素:对于动态添加的子元素,使用事件代理可以确保这些新元素也能受到事件处理程序的控制,而无需重新绑定事件。
- 代码简洁:通过将事件处理程序绑定到共同的父元素上,可以减少重复的代码量,提高代码的可维护性。
示例代码如下所示:
// 父元素
const parentElement = document.getElementById('parent');
// 事件代理
parentElement.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') { // 假设子元素是 li 元素
console.log('子元素被点击:', event.target.textContent);
}
});
在上面的示例中,当点击 parentElement
的子元素 li
时,事件会冒泡到 parentElement
上,然后通过判断点击的目标元素是否为 li
元素来执行相应的操作。
总结
JavaScript 事件流是前端开发中非常重要的一部分,深入理解事件流对于我们更好地处理事件、提高代码质量和性能至关重要。掌握事件流需要我们对事件处理机制有深刻的理解,并通过实践和调试来不断提高代码的质量。希望本文对你了解 JavaScript 事件流有所帮助,祝愉快编程!
(假如您也和我一样,在准备春招。
欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,
分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!”)