js事件机制:监听、捕获、冒泡与委托

众所不周知,在浏览器中,原生的 DOM 事件流包含三个阶段:捕获阶段、目标阶段、冒泡阶段。

而 DOM 事件流的执行流程,也严格按照:事件捕获 -> 事件目标 -> 事件冒泡 的顺序来执行。

本文将结合代码示例,详细讲解事件监听,事件捕获,事件冒泡和事件委托机制。

一、事件监听函数addEventListener

在JavaScript中,事件监听通过 addEventListener 来实现,这是现代浏览器推荐的标准方式(DOM2 级事件),与传统的 DOM0 级事件(如 onclick)相比,它提供了更灵活的控制能力。

1.1 监听范围判定

事件监听的传播遵从以下原则:当一个元素接收到事件的时候,它会把他接收到的事件传给自己的父级,并一层层向外传递,直到传到 window,而父元素被触发时则不会将事件传给子元素。

为了更好理解,我们以下面这两个盒子为例,其中,青色盒子是粉色盒子的子元素。

代码示例:

javascript 复制代码
  document.getElementById('parent').addEventListener('click',function(event){
            console.log("我是大盒子")
        })
        
  document.getElementById('child').addEventListener('click',function(event){
            console.log("我是小盒子")
        })

此时,当我们点击小盒子,在控制台我们能看到:

而当我们点击大盒子时,运行结果为:

由此,可以验证:当子元素接收到事件的时候,它会把他接收到的事件传给自己的父级,而父元素被触发时则不会将事件传给子元素。

1.2 监听函数的参数

从上面的代码我们可以看到,一般情况下,addEventListener()默认只有两个参数,其中,一个是绑定的事件,一个是事件触发后要执行的事件处理函数,即:

arduino 复制代码
addEventListener('event','function')

然而,真的是这样吗?事实上,addEventListener()还存在隐藏的第三个参数:useCapture,这个参数的作用是决定在事件冒泡阶段要不要调用事件处理函数。

arduino 复制代码
addEventListener('event','function','useCapture')

默认情况下,useCapture的值为false,这表示在事件冒泡阶段调用事件处理函数,当我们将它改为true时,那么,它将变为在事件捕获阶段调用处理函数。

那么,这有什么区别呢?我们来看个例子:

我是例子:

javascript 复制代码
  document.getElementById('parent').addEventListener('click',function(event){
            console.log("我是大盒子")
        },false)
        
  document.getElementById('child').addEventListener('click',function(event){
            console.log("我是小盒子")
        },false)

此时,我们将第三个参数useCapture设置为false(也就是默认值),这时允许事件冒泡,我们点击小盒子,在可以看到:

此时的打印结果为小盒子->大盒子,这个打印结果完全符合事件冒泡阶段事件从内而外的处理顺序,而相同的代码,我们将false改为true后,打印结果为:

此时输出顺序变为了大盒子->小盒子,也符合阻止事件冒泡后的事件执行顺序。


二、事件捕获与事件冒泡

事件捕获 :它是 DOM 事件流的第一个阶段,当鼠标点击目标事件后,事件将会从最外层的 window 或 document 开始,自外而内地传播到目标元素,其触发时机要早于目标元素的事件处理。

事件冒泡 :它是事件流的最后一个阶段,它和事件捕获相似,但是事件传播方向相反,它的事件是从目标元素开始,自内而外 地传播到根节点(如 document 或 window)。

还是以上面的代码为例,不过这次我们进行对照测试:

javascript 复制代码
//阻止事件冒泡组
document.getElementById('parent').addEventListener('click',function(event){
            console.log("大盒子---向内")
        },true)
        
document.getElementById('child').addEventListener('click',function(event){
            console.log("小盒子---向内")
        },true)

//允许事件冒泡组
document.getElementById('child').addEventListener('click',function(event){
            console.log("小盒子---向外")
        },false)
        
document.getElementById('parent').addEventListener('click',function(event){
            console.log("大盒子---向外")
        },false)
        

由上面地结果我们可以得知:事件捕获与事件冒泡是比较相似的,区别在于事件传播的方向不同。


四、事件委托

4.1 概念介绍

事件委托 :事件委托就是利用冒泡机制,将子元素的事件处理委托给父元素进行处理 ,因为依赖冒泡机制,所以,如果阻断了事件冒泡,事件委托也无法实现。

事件委托的作用:

先想象一下,我们有一个列表,里面有10000项数据,那么,我想要监听每一个数据的变化,按照正常的想法,我们是不是应该使用 for 循环或其他方式,分别给这10000个元素都添加一个监听事件?

然而,事件委托混合地解决了这个问题。

从上面的事件捕获和事件冒泡我们已经得知,当子元素触发事件时,会将事件传递给父元素,那么,根据这个原理,我们就可以只在父元素上添加一个监听事件,通过这种方法,达到监听所有子元素的目的。

4.2 优缺点分析

优点:

1.内存方面

  • 事件委托只需在父元素上绑定一个事件监听器,而不是为每个子元素单独绑定,这极大减少内存的消耗。

  • 事件委托适合动态列表、表格等大量子元素的场景。

2.代码方面

  • 新增的子元素无需重新绑定事件,天然支持动态 DOM 结构,如: // 点击新增的按钮依然能触发事件 document.getElementById('list').addEventListener('click', (e) => { if (e.target.matches('button')) { console.log('按钮被点击'); } });

  • 事件委托避免循环绑定事件,减少重复代码。

  • 有利统一管理事件逻辑,降低维护成本。

3.性能方面

  • 减少浏览器事件监听器的数量,提升页面响应速度(尤其对移动端更友好)

缺点

1.事件目标判断复杂

  • 比如需要通过 event.target 或 event.currentTarget 精确识别触发元素,嵌套结构时可能需配合 closest() 方法。

    less 复制代码
    // 如果子元素内有嵌套的 span,需额外判断
    if (e.target.closest('.btn')) { ... }

2.不适合所有事件类型

  • 部分事件不冒泡 (如 focus、blur、load 等),无法使用事件委托。
  • 解决方案:用冒泡替代事件(如 focusin 代替 focus)。

3.事件阻止需谨慎

  • 如果在委托的父元素上调用 stopPropagation(),会影响其他子元素的正常事件流。

相关推荐
w2sfot2 小时前
【JS加密在线】jsjiami.online(让JavaScript 更安全!)
开发语言·javascript·安全
杨杰3986 小时前
我给嵌入式设备写了个 6MB 的 JavaScript 运行时(xuan)
javascript
只睡四小时7 小时前
AI 生成 PPTX:11 页课件编译出 429 个形状
javascript·python·pptx·ai生成ppt·ooxml
shmily麻瓜小菜鸡8 小时前
Axios 中 params 与 data 的区别与原理
javascript·typescript
isha8 小时前
Univer拆解——从类与方法的视角分析
前端·javascript
变与不变8068 小时前
GET请求知识详解
前端·javascript
哎哟喂呢哈10 小时前
VedioJS 10
javascript·html5
夏幻灵18 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
三天不学习19 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
xcs1940519 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js