事件机制与委托:从冒泡捕获到高效编程的奇妙之旅

引入

今天在探索JavaScript事件机制时,我仿佛闯入了DOM世界的魔法学院。这里的事件就像调皮的魔法精灵,它们的行为规律让我大开眼界!先来看看这个神奇的现象:

xml 复制代码
<!-- 魔法盒子实验 -->
<div id="parent" style="background:blue;width:200px;height:200px;">
  <div id="child" style="background:red;width:100px;height:100px"></div>
</div>

<script>
  parent.addEventListener('click', () => console.log('父元素被点啦!'), false)
  child.addEventListener('click', () => console.log('子元素被戳啦!'), false)
</script>

当我点击红色子盒子时,控制台居然依次打印:

咦?明明只点了子元素,为什么父元素也"感觉"到了?原来这就是事件冒泡的魔法效果!事件就像水中的气泡,从触发点(子元素)慢慢浮到顶层(父元素)。不过别急,这只是故事的一半...

事件的三段奇妙旅程

DOM世界的事件传播就像一场精心编排的舞台剧,分为三个精彩幕次:

  1. 捕获阶段:事件从window逐级向下"潜行"到目标元素
  2. 目标阶段:事件到达目标元素
  3. 冒泡阶段:事件从目标元素向上"浮出"到window

当我们使用addEventListener时,第三个参数就是控制魔法时机的开关: 我们可以看MDN的官方文档怎么介绍addEventListener

  • true:在捕获阶段触发(魔法自上而下)
  • false(默认):在冒泡阶段触发(魔法自下而上)

通俗来说,我们当用true会先触发父元素事件,再到子元素事件,当是false则相反,当然这是建立在父元素和子元素绑定了同一个事件,且在子元素身上触发了

结论

  1. addEventListener 第3个参数决定了事件是在捕获阶段触发还是在冒泡阶段触发
  2. addEventListener 第3个参数为 true 表示捕获阶段触发,false 表示冒泡阶段触发,默认值为 false
  3. 事件流只会在父子元素具有相同事件类型时才会产生影响
  4. 绝大部分场景都采用默认的冒泡模式(其中一个原因是早期 IE 不支持捕获)

阻止冒泡e.stopPropagation()

事件对象.stopPropagation()

阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。

此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效

xml 复制代码
<body>
  <div class="father">
    <div class="son"></div>
  </div>
  <script>
    const father = document.querySelector('.father')
    const son = document.querySelector('.son')

    document.addEventListener('click', function () {
      alert('我是爷爷')
    })

    father.addEventListener('click', function () {
      alert('我是爸爸')
    })
    son.addEventListener('click', function (e) {
      alert('我是儿子')
      e.stopPropagation()   //阻止冒泡
    })

  </script>
</body>

我们来看效果:

可以看到当我们点击子元素,是没有触发祖先事件的,这就是e.stopPropagation()的作用

结论:

事件对象中的 ev.stopPropagation 方法,专门用来阻止事件冒泡。

鼠标经过事件:

mouseover 和 mouseout 会有冒泡效果

mouseenter 和 mouseleave 没有冒泡效果 (推荐)

DOM0 vs DOM2:魔法咒语的进化史

在魔法学院的历史上,曾有两种施法方式:

xml 复制代码
<!-- DOM0 古早咒语(已过时) -->
<a onclick="doSomething()">点我试试</a>

<!-- DOM2 现代魔法 -->
element.addEventListener('click', doSomething)

为什么现代巫师都用DOM2呢?因为DOM0有三个致命缺陷:

  1. 只能绑定一个处理函数(最后覆盖前面的)
  2. HTML和JS耦合严重(违反职责分离原则)
  3. 无法控制事件传播阶段

而DOM2的addEventListener就像多功能魔法杖:

  • 可绑定多个处理函数
  • 可精确控制捕获/冒泡阶段
  • 代码可维护性更高

DOM2级事件实现了模块化分离,我们就应该html,css,js 分离来写,这样的代码可读性是十分优雅的

事件委托:以一敌百的智慧

当我遇到这个需求时:

"给列表中所有li添加点击事件新手巫师可能会这样:

ini 复制代码
const lis = document.querySelectorAll('li');
lis.forEach(li => {
  li.addEventListener('click', handleClick);
});

我们的浏览器是存在一个event loop,我们这样操作就是注册很多事件,每一个事件都存在监听器

当列表有1000项时,相当于创建了1000个事件监听器!这就像雇佣1000个卫兵每人看守一颗石子------效率低下到让国王破产。

智慧的老巫师微微一笑,祭出了事件委托大法:

xml 复制代码
<ul id="myList">
  <li>item1</li>
  <li>item2</li>
  <!-- 更多li... -->
</ul>

<script>
  document.getElementById('myList').addEventListener('click', e => {
    if (e.target.tagName === 'LI') {
      console.log(`你点击了:${e.target.textContent}`);
    }
  });
</script>

这里的神奇之处在于:

  1. 只在父元素ul上设置一个监听器
  2. 通过e.target识别实际点击的元素
  3. 自动处理动态添加的子元素(无需重新绑定)

这就像在城堡门口设一个智能安检门,自动识别并处理不同人员,而不是给每个房间都派卫兵!

事件委托是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率。

利用事件流的特征,可以对上述的代码进行优化,事件的的冒泡模式总是会将事件流向其父元素的,如果父元素监听了相同的事件类型,那么父元素的事件就会被触发并执行,正是利用这一特征对上述代码进行优化,

我们来看效果,一样可以满足我们的需求

target vs currentTarget:魔法镜的奥秘

在事件委托中,理解这两个属性至关重要:

  • e.target:实际触发事件的元素(事件起源)
  • e.currentTarget:当前处理事件的元素(绑定监听器的元素)
javascript 复制代码
myList.addEventListener('click', function(e) {
  console.log(e.target);      // 被点击的<li>
  console.log(e.currentTarget); // 永远是<ul>
});

这就像快递配送:

  • target是原始发货人(工厂)
  • currentTarget是当前中转站(配送中心)

React中的魔法优化

在React王国里,事件委托被用到了极致。所有的React事件都委托到 #root容器

javascript 复制代码
// React内部相当于这样做了:
document.getElementById('root').addEventListener('click', e => {
  // 通过虚拟DOM找到对应组件处理
});

这种设计带来三大优势:

  1. 内存高效:整个应用共用少量监听器
  2. 动态无忧:组件动态增减不影响事件
  3. 一致行为:统一处理事件冒泡逻辑

结语:事件机制的哲学

从蓝色父盒子与红色子盒子的互动,到高效处理动态列表,事件机制教会我们编程的重要哲学:

优秀的代码不是控制每个个体,而是建立优雅的响应系统

就像DOM世界的事件流,我们的人生也充满各种"事件"。理解它们的传播机制,学会用"委托"智慧处理问题,才能在代码与生活的复杂性中游刃有余。

相关推荐
mldong7 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排8 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60618 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角9 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!10 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰10 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi11 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen12 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马13 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu13 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss