深入理解 JavaScript 事件流机制:冒泡、捕获及事件代理事件触发过程是 JavaScript 中非常重要的概念,理解它可以帮助我们更好地处理用户交互和网页响应。在 JavaScript 中,事件触发是通过事件流(Event Flow)来实现的。事件流描述了从事件发生到事件被处理的整个过程,其中包括捕获阶段、目标阶段和冒泡阶段。
三大阶段(接新娘三个过程)捕获阶段(去接新娘)事件触发的第一阶段是捕获阶段。在这个阶段中,事件从最外层的 window 对象开始向下传播,直到达到真正发生事件的元素。在这个过程中,会依次触发经过的每个元素上注册的捕获事件处理函数。捕获阶段的目的是为了能够在事件到达目标之前对其进行预处理。
目标阶段(看到新娘)一旦事件到达目标元素,就进入了目标阶段。在这个阶段中,事件会触发目标元素上注册的事件处理函数。目标阶段是事件触发的核心阶段,通常我们在这里处理具体的业务逻辑。
冒泡阶段(带回新娘)事件触发的最后阶段是冒泡阶段。在这个阶段中,事件从目标元素开始向上传播,直到达到最外层的 window 对象为止。在这个过程中,会依次触发经过的每个元素上注册的冒泡事件处理函数。冒泡阶段的目的是为了能够在事件到达最外层之前对其进行后续处理。
需要注意的是(接亲注意事项)捕获事件和冒泡事件是互斥的,即同一个事件只能在其中一个阶段触发,而不能同时触发。默认情况下,事件是按照冒泡过程进行处理的。(新娘没在不显示)我们也可以通过调用 addEventListener 方法的第三个参数来设置事件的处理方式。如果将第三个参数设置为 true,则表示事件在捕获阶段进行处理;如果将第三个参数设置为 false 或不传入该参数,则表示事件在冒泡阶段进行处理。默认冒泡事件实例(带回新娘实战)
#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;
}
let a = document.getElementById('a')
let b = document.getElementById('b')
let c = document.getElementById('c')
a.addEventListener('click', () => {
console.log('a被点击');
},)
b.addEventListener('click', (event) => {
console.log('b被点击');
}, )
c.addEventListener('click', (event) => {
console.log('c被点击2');
},)
控制台会打印的顺序为 c,b,a
调用第三个参数(考虑要不要阻止接亲)a.addEventListener('click', () => {
console.log('a被点击');
},true)
b.addEventListener('click', (event) => {
console.log('b被点击');
}, true)
c.addEventListener('click', (event) => {
console.log('c被点击2');
},true)像这样调用第三个参数可以阻止默认冒泡行为,一般情况下,代码写在最后面的不需要传递第三个参数。
阻止默认行为方法(阻止他们)有时候,在处理事件的过程中,我们可能需要阻止事件的默认行为,例如点击链接时阻止跳转页面。在 JavaScript 中,我们可以使用 event.stopPropagation() 方法来阻止事件流的传播。调用该方法后,事件将不再继续传播,不会触发其他元素上的事件处理函数。
在 b 内添加该方法可以阻止 b 之后的事件流传播:
b.addEventListener('click', (event) => {
console.log('b被点击');
event.stopPropagation()
}, )另外,还有一个类似的方法叫做 event.stopImmediatePropagation()。与 event.stopPropagation() 不同的是,调用 event.stopImmediatePropagation() 方法不仅会阻止事件传播,还会阻止同一个元素上绑定的其他相同类型事件的触发。
添加一个新的 c 事件,该方法会阻止 “c 被点击” 触发,不过该方法也同样有执行的先后顺序(如果写在 “c 被点击” 里面,他不会阻止 “c 被点击 2” 事件流发生)。
c.addEventListener('click', (event) => {
console.log('c被点击2');
event.stopImmediatePropagation()
},)
c.addEventListener('click', (event) => {
console.log('c被点击');
}, )事件代理(事件委托)事件代理(也称为事件委托)是一种常用的优化技术,用于管理大量相似元素上的事件处理。它通过将事件处理程序绑定到它们共同的父元素上,利用事件冒泡机制来处理事件。这样做的好处是可以减少事件处理程序的数量,提高性能和代码的可维护性。
使用事件代理,我们可以将事件处理程序绑定到父元素上,并通过事件对象的 target 属性来判断具体触发事件的子元素。这样,无论子元素的数量如何变化,我们都只需要关心父元素上的事件处理逻辑,大大简化了代码。
- a
- b
- c
- d
- e
let ul = document.getElementsByTagName('ul')[0]
ul.addEventListener('click', (event) => {
// 此时事件流从哪里来到了ul上
console.log(event.target.innerHTML);
})
在这段代码中,通过 document.getElementsByTagName('ul')[0] 获取到了页面上第一个
- 元素,并给它添加了一个点击事件的监听器。当用户点击
- )开始传播,然后经过父级元素
- ,直到达到最外层的 window 对象。
在这段代码中,我们通过事件对象 event.target 来获取到实际触发事件的元素。在事件处理函数中,输出了 event.target.innerHTML 的值,即触发事件的元素的内容。所以,当点击事件发生时,会打印出被点击的子元素的内容。
需要注意的是,事件代理是利用事件冒泡机制来管理事件处理程序的一种技术。通过将事件处理程序绑定到父元素上,可以减少事件处理程序的数量,并且不受子元素的变化影响。在这个例子中,我们将点击事件的监听器绑定到了
- 元素上,而不是绑定到具体的子元素上,从而实现了事件代理的效果。
记录一下复杂的实现方法
// 点哪一个li,就log出它的内容
let lis = document.getElementsByTagName('li')
lis.__proto__.forEach = Array.prototype.forEach
Array.from(lis).forEach(li => {
li.addEventListener('click', () => {
console.log(li.innerHTML);
})
});
- 元素或其内部的任何子元素时,事件会触发该监听器。
在点击事件发生时,事件流从最具体的目标元素开始,逐级向上冒泡。因此,事件从触发点击事件的具体子元素上(例如