🚀 JS事件机制大揭秘:从“橘子”报警到“列表”瘦身,前端老鸟都在偷笑的秘密!

🚀 JS事件机制大揭秘:从"橘子"报警到"列表"瘦身,前端老鸟都在偷笑的秘密!

导读 :你以为点击只是点击?NO!在JS的世界里,一次点击是一场从documenttarget再回来的"长途旅行"。今天咱们就拿着你提供的代码,像侦探一样扒一扒事件捕获、冒泡、stopPropagation 的底裤,顺便看看怎么用事件委托给内存做个"抽脂手术"!💃🕺


🎬 第一幕:事件的"三生三世"------捕获、目标、冒泡

首先,咱们得纠正一个误区:页面虽然是平的,但DOM树是立体的! 当你点击页面上的一个按钮(比如那个蓝色的child),浏览器可不是只喊一声"嘿,被点了!",而是上演了一出三阶段大戏

  1. 捕获阶段 (Capturing Phase) 📉
    • 剧情 :事件从老大 document 开始,一层层往下传,"注意啦注意啦,有人要点击啦!"一直传到目标元素的父节点。
    • 特点:这是"自上而下"的巡视。
  2. 目标阶段 (Target Phase) 🎯
    • 剧情 :事件终于到达了真正的目的地------你点击的那个元素(event.target)。
    • 特点:这是"正主"登场。
  3. 冒泡阶段 (Bubbling Phase) 📈
    • 剧情 :事情办完了,消息开始"往上汇报"。从目标元素开始,一层层往上传回 document,"报告老大,刚才那个蓝色方块被点了!"
    • 特点 :这是"自下而上"的反馈,默认情况下,我们的监听器都在这阶段干活!

🧐 第二幕:代码破案------capturestopPropagation 的爱恨情仇

咱们来看看你提供的第一段"神代码",里面藏着两个大坑和一个"橘子"陷阱!🍊

🕵️‍♂️ 案发现场还原

html 复制代码
<body onclick="alert('橘子')"> <!-- 陷阱1:DOM 0级事件 -->
    <div id="parent">
        <div id="child"></div>
    </div>
    <script>
        // 监听器 A (Parent)
        document.getElementById('parent').addEventListener('click', function(){
            event.stopPropagation(); // 关键杀手锏!
            console.log('parent click');
        }, false); // 参数3为false,默认冒泡阶段执行

        // 监听器 B (Child)
        document.getElementById('child').addEventListener('click', function(){
            console.log('child click');
        }, false); // 参数3为false,默认冒泡阶段执行
    </script>
</body>

🔍 深度解析

1. addEventListener 的第三个参数:useCapture

这个参数决定了你的监听器是在捕获阶段 还是冒泡阶段被执行。

  • false (默认) 👉 冒泡阶段。也就是等事件从子元素传上来时再执行。大多数时候我们都用这个。
  • true 👉 捕获阶段。也就是事件还没到子元素,路过父元素时就被截胡执行了。

💡 记忆口诀true 是"真"想早点拦下来(捕获),false 是"否"则等它冒上来(冒泡)。

2. event.stopPropagation():霸道总裁的"闭嘴"指令

在上面的代码中,parent 的监听器里调用了 event.stopPropagation()

  • 作用:阻止事件继续传播(无论是向上冒泡还是向下捕获)。
  • 效果:一旦执行,后面的旅程全部取消!
🎬 实际演出流程(点击蓝色 child
  1. 捕获阶段document -> html -> body -> parent
    • 此时 parent 的监听器设置了 false(冒泡),所以不执行
    • body 上有个 onclick="alert('橘子')" (DOM 0级),它默认也是冒泡,所以不执行
  2. 目标阶段 :到达 child
    • 触发 child 的监听器。控制台输出:child click ✅。
  3. 冒泡阶段 :从 child 向上传到 parent
    • 触发 parent 的监听器。
    • 执行 console.log('parent click'),输出:parent click ✅。
    • 紧接着执行 event.stopPropagation() !🛑 旅行结束!
  4. 后续 :事件本该继续冒泡到 body 触发 alert('橘子'),但因为被 stopPropagation() 拦住了,"橘子"永远不会出现! 🍊❌

⚠️ 注意 :如果你把 parentchild 的第三个参数改成 true,执行顺序就会大变天!捕获阶段的代码会最先执行,甚至可能在目标阶段之前就拦截了事件。


💰 第三幕:内存瘦身术------事件委托 (Event Delegation)

再看你的第二段代码,这是一个经典的**"省钱"**案例。

❌ 笨办法:给每个 <li> 都装监控

javascript 复制代码
const lis = document.querySelectorAll('#list li');
for(let i=0; i<lis.length; i++){
    lis[i].addEventListener('click', function(){
        console.log(this.innerHTML);
    });
}
  • 缺点 :如果有1000个 <li>,就要注册1000个监听器!内存开销巨大,就像给小区每户人家门口都装个保安,累死且费钱。而且,如果动态新增了 <li>,还得重新绑定,麻烦死了。

✅ 聪明办法:只在门口装一个监控(事件委托)

利用冒泡机制 ,我们只需要在父元素 <ul> 上装一个监听器。

javascript 复制代码
document.getElementById('list').addEventListener('click', function(event){
    console.log('------');
    // event.target 才是真正被点击的那个元素(可能是 li,也可能是 li 里面的 span)
    console.log(event.target, event.target.innerHTML);
});
🌟 核心原理
  1. 点击任何一个 <li>,事件会冒泡 到父元素 <ul>
  2. <ul> 的监听器被触发。
  3. 通过 event.target 属性,我们可以精准识别出到底是哪个"小家伙"触发了事件。
    • event.target:事件发生的源头(实际点击的元素)。
    • this (在回调中):当前绑定监听器的元素(这里是 <ul>)。
🚀 优势大比拼
特性 传统绑定 (每个li) 事件委托 (父元素ul)
内存占用 高 (N个监听器) 极低 (1个监听器)
动态元素支持 需重新绑定 自动支持 (新增li也能触发)
性能 初始化慢 初始化快
代码简洁度 啰嗦 优雅

💡 掘金热梗:这就叫"一人得道,鸡犬升天"......啊不对,是"父债子偿"......也不对,是**"父监子行"**!只要爸爸(父元素)盯着,儿子(子元素)干啥都知道。


📝 总结:前端面试必背"三字经"

  1. 流三阶:捕获 ➡️ 目标 ➡️ 冒泡。默认监听在冒泡。
  2. 参真假addEventListener 第三个参数,true 抢跑(捕获),false 等泡(冒泡)。
  3. 停传播stopPropagation() 是路霸,谁调它,后面的路都不通了(包括DOM 0级事件)。
  4. 委托好 :别给子元素逐个绑,父元素上一个搞定,配合 event.target 走天下。
  5. 内存省:监听器多了会"爆炸",事件委托是"减肥药"。

🎁 彩蛋:为什么不能给集合直接加监听?

你提到的"事件监听不可以在集合上",是因为 querySelectorAll 返回的是 NodeList(类似数组),它本身不是 DOM 节点,没有 addEventListener 方法。你必须遍历它,或者直接用事件委托在父节点上监听。


最后送大家一句话 : 理解事件机制,不仅是为了解决Bug,更是为了写出**"丝滑""省钱"**的代码。下次面试官问你事件冒泡,你就把这出"橘子"和"列表"的大戏讲给他听,Offer 绝对稳了!😎

互动时间 :你在项目中用过 stopPropagation 踩过什么坑吗?欢迎在评论区分享你的"血泪史"!👇

相关推荐
用户5757303346242 小时前
💎 JS 中的“隐形人”:Symbol 数据类型深度解密!从命名冲突到隐私保护
javascript
掘金安东尼2 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼2 小时前
Next.js 企业级落地
前端·javascript·面试
掘金安东尼2 小时前
React 性能优化完全指南 2026
前端·javascript·面试
小霖家的混江龙2 小时前
从 0 到 1 实现一个 useState
前端·javascript·react.js
晓得迷路了2 小时前
栗子前端技术周刊第 118 期 - Oxfmt Beta、Angular GitHub stars、React 基金会...
前端·javascript·react.js
摸鱼的春哥3 小时前
Agent教程14:记忆才是Agent开发的核心
前端·javascript·后端
明月_清风3 小时前
Clipboard API 深度实战:如何同时存入“纯文本”和“富文本”两种格式?
前端·javascript
明月_清风3 小时前
权限陷阱:为什么你的“点击复制”在某些浏览器或 iframe 里会失效?
前端·javascript