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

引入

今天在探索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世界的事件流,我们的人生也充满各种"事件"。理解它们的传播机制,学会用"委托"智慧处理问题,才能在代码与生活的复杂性中游刃有余。

相关推荐
zwjapple6 小时前
docker-compose一键部署全栈项目。springboot后端,react前端
前端·spring boot·docker
像风一样自由20208 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem8 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊9 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术9 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing9 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止9 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall9 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴9 小时前
简单入门Python装饰器
前端·python
袁煦丞10 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作