JS—事故委托:3分钟掌握事故委托

打印 上一主题 下一主题

主题 959|帖子 959|积分 2877

个人博客:haichenyi.com。感谢关注

一. 目录



  • 一–目录
  • 二–核心原理
  • 三–事故的组成
  • 四–如何实现
  • 五–高频使用场景
  • 六–最佳实践指南

二. 核心原理

  什么是事故的委托? 事故委托就是利用冒泡机制,把子元素的事故统一委托给父元素处理的优化机制。在部分场景,能显著提升性能。


  • 事故冒泡:当子元素触发事故(如:点击),会统一从子元素从下往上传递,不停传递到document
  • 父元素统一监听:通过给父元素绑定事故监听器,利用event.target识别实际触发的子元素,制止为每个子元素添加事故。
以上就有两个问题:什么是事故冒泡?怎么给父元素绑定监听器?

三. 事故的组成

  JavaScript中一个事故是由三个阶段组成:捕获阶段,目标阶段,冒泡阶段。
阶段触发次序监听方式捕获阶段1addEventListener(type, handler, true)目标阶段2直接绑定到目标元素的事故冒泡阶段3addEventListener(type, handler, false) 举个栗子:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset='utf-8'>
  5.     <title>事件委托</title>
  6.     <meta name='viewport' content='width=device-width, initial-scale=1'>
  7.     <style>
  8.         .grandparent {
  9.             width: 200px;
  10.             height: 200px;
  11.             background-color: aqua;
  12.         }
  13.         .parent {
  14.             width: 100px;
  15.             height: 100px;
  16.             background-color: blanchedalmond;
  17.         }
  18.         .child {
  19.             width: 50px;
  20.             height: 50px;
  21.             background-color: brown;
  22.         }
  23.     </style>
  24. </head>
  25. <body>
  26.     <div id="grandparent" class="grandparent">
  27.         <div id="parent" class="parent">
  28.             <button id="child" class="child"></button>
  29.         </div>
  30.     </div>
  31. </body>
  32. <script>
  33.     //捕获阶段监听
  34.     document.getElementById('grandparent').addEventListener('click', (event) => {
  35.         console.log("grandparent 捕获阶段");
  36.     }, true)
  37.     document.getElementById('parent').addEventListener('click', (event) => {
  38.         console.log("parent 捕获阶段");
  39.     }, true)
  40.     document.getElementById('child').addEventListener('click', (event) => {
  41.         console.log("child 捕获");
  42.     }, true)
  43.     //冒泡阶段监听
  44.     document.getElementById('child').addEventListener('click', (event) => {
  45.         console.log("child 冒泡");
  46.     }, false)
  47.     document.getElementById('parent').addEventListener('click', (event) => {
  48.         console.log("parent 冒泡阶段");
  49.     }, false)
  50.     document.getElementById('grandparent').addEventListener('click', (event) => {
  51.         console.log("grandparent 冒泡阶段");
  52.     }, false)
  53. </script>
  54. </html>
复制代码



  上面这个简朴的例子的就是一个事故的整个执行过程。三张图分别点击三个区域的打印
一共三个元素,分别监听了他们的click事故。

  • 当我点击child的时间,我的目标元素就是child。会先执行:grandparent(捕获)—>parent(捕获)—>child(捕获)—>child冒泡—>parent(冒泡)—>grandparent(冒泡)
  • 当我点击parent的时间,我的目标元素就是parent。执行次序:grandparent(捕获)—>parent(捕获)—>parent(冒泡)—>grandparent(冒泡)
  • 当我点击grandparent的时间,我们的目标元素就是grandparent。执行次序:grandparent(捕获)—>grandparent(冒泡)
  有人就会问了,三个阶段,为什么没有目标阶段?什么是目标阶段?表格上面就说了。目标阶段直接绑定在目标元素上。 可以这么理解。当我点击child元素的时间,当事故传递到child身上的时间,目标阶段就已经触发了。目标阶段是一个独立的阶段,捕获和冒泡是事故传递的路径。
  说白了,就是Android内里的事故触摸的传递。down事故:由activity到目标view,up事故:由view到activity的过程。return true还是false,表示是否需要自己消耗整个事故,不继承传递。
  捕获对应down事故,冒泡对应up事故。
范例向下事故向上事故是否自己消耗AndroidDownUPreturn true/falseJavaScript捕获阶段冒泡阶段stopPropagation   当父子view都添加了点击事故,当我触发子view的点击事故的时间,不想触发父view的点击事故,该怎么做呢?阻止事故冒泡,也就是上面的说,自己消耗这个事故,不让它继承传递了。好比:
  1. document.getElementById('child').addEventListener('click', (event) => {
  2.         console.log("child 冒泡阶段");
  3.         //直接给目标view,添加一行这个代码,表示组织事件冒泡,也就是不继续传递了。
  4.         event.stopPropagation()
  5.     }, false)
复制代码

特殊的事故

有一些事故是没有冒泡事故的
事故范例办理方案focus/blur使用 focusin/focusout(支持冒泡)mouseenter/mouseleave改用 mouseover/mouseout + 条件判断load/unload仅作用于目标元素
四. 如何实现

  我们开头举的例子使用事故委托,需要怎么改呢?
  1. document.getElementById('grandparent').addEventListener('click', (event) => {
  2.         // 识别实际点击的目标元素
  3.         const target = event.target.closest('#child, #parent, #grandparent');
  4.         if (!target) return; // 非目标元素则退出
  5.         // 根据元素 ID 执行不同逻辑
  6.         switch (target.id) {
  7.             case 'child':
  8.                 console.log('委托处理:child 被点击');
  9.                 // event.stopPropagation(); // 如需阻止冒泡在此处调用
  10.                 break;
  11.             case 'parent':
  12.                 console.log('委托处理:parent 被点击');
  13.                 break;
  14.             case 'grandparent':
  15.                 console.log('委托处理:grandparent 被点击');
  16.                 break;
  17.         }
  18.     });
复制代码
  如上的代码,我们给grandparent设置点击事故,然后利用event对象,判断点击的组件时哪个,就触发对应的逻辑即可。就是这么简朴。
  核心思想,就是找到你想要设置点击事故view,他们功能的父view,然后给父view设置点击事故,利用事故分发的原理,判断即可。
这里有几点需要留意:
属性说明event.target始终指向实际触发事故的viewevent.currentTarget指向当前绑定监听事故的view(在本例中也就是上面说的grandparent) 方法作用范围说明event.stopPropagation()当前阶段阻止事故继承传播(不影响同阶段其他监听器)event.stopImmediatePropagation()所有阶段立刻停止所有后续监听器执行
五. 高频使用场景

动态列表高频更新

当 v-for 渲染的列表频繁增删时,单独绑定事故会导致重复解绑/绑定。
优化方案: 委托到父容器,利用 event.target 判断触发元素
  1. <div class="list-container" @click="handleDelegateClick">
  2.   <div v-for="item in dynamicList" :data-id="item.id">{{ item.text }}</div>
  3. </div>
  4. methods: {
  5.   handleDelegateClick(event) {
  6.     const target = event.target.closest('[data-id]');
  7.     if (!target) return;
  8.     const itemId = target.dataset.id;
  9.     // 根据 id 找到对应数据
  10.   }
  11. }
复制代码
复杂子元素布局

当子元素内部包含交互元素时,直接 @click 会导致事故目标判断困难:
  1. <div class="card" @click="handleCard">
  2.   <button @click.stop="handleDelete">删除</button>
  3. </div>
复制代码
问题: 点击按钮会同时触发 handleCard 和 handleDelete
办理方案: 统一委托到父级,通过类名区分目标
  1. <div class="cards-container" @click="handleContainerClick">
  2.   <div class="card" data-type="card">
  3.     <button data-type="deleteBtn">删除</button>
  4.   </div>
  5. </div>
  6. methods: {
  7.   handleContainerClick(event) {
  8.     const type = event.target.dataset.type;
  9.     switch(type) {
  10.       case 'card':
  11.         // 处理卡片点击
  12.         break;
  13.       case 'deleteBtn':
  14.         // 处理删除按钮
  15.         break;
  16.     }
  17.   }
  18. }
复制代码
超长列表性能优化

渲染 1000+ 个带点击事故的元素时,单独绑定会导致内存激增。
绑定方式内存占用滚动流通度单独 @click高卡顿事故委托低流通 ps:并不是所有的列表都需要,列表充足长才需要。一样平常不需要。

六. 最佳实践指南

场景推荐方案小型静态列表 (<100 项)直接使用 @click动态增删列表父级容器委托 + dataset 标志高频交互的可滚动长列表虚拟滚动 + 事故委托需要精确控制事故传播的场景委托 + event.stopPropagation 决策树:
元素数量 > 500? → 是 → 必须用事故委托
↓ 否
是否需要动态更新? → 是 → 推荐委托
↓ 否
是否有复杂子布局? → 是 → 建议委托
↓ 否
直接使用 @click
PS:整篇文章,精炼一下,如下

  • 事故委托的核心原理:利用冒泡机制和事故统一交给父view处理
  • 事故的组成三个阶段:捕获,目标,冒泡
  • 捕获阶段:从上往下传递,冒泡阶段冲下往上传递。
  • 怎么实现事故委托:父view统一监听,使用event.target属性去判断。使用event.stopPropagation()阻止事故传递
  • 什么情况下需要使用事故委托?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

罪恶克星

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表