ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Modern JavaScript Tutorial 事件委托(Event Delegation)实战指南:用单个处理器掌控动态 DOM

Modern JavaScript Tutorial 事件委托(Event Delegation)实战指南:用单个处理器掌控动态 DOM 文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载事件委托是 JavaScript DOM 事件领域最强大的处理模式之一其核心思想是与其给大量相似元素逐个绑定处理器不如在它们的共同祖先上挂载一个统一处理器再通过event.target判断事件真正发生的位置并执行相应逻辑。本文基于 Modern JavaScript Tutorial 仓库的 事件委托章节 及其配套实战任务完整讲解委托原理、closest()定位技巧、data-action动作分发以及data-counter、data-toggle-id等声明式行为模式的实现。读完本文你将掌握如何用更少的代码处理任意数量甚至动态增删的元素并能在表格高亮、菜单分发、树形折叠、可排序表格等真实场景中直接落地。事件委托之所以可行前提是前一章 冒泡与捕获Bubbling and Capturing 中介绍的冒泡机制当事件发生在某个元素上时它会先触发该元素自身的处理器然后逐级向上触发父元素、祖先元素……直到document对象。正因为事件会像水中的气泡一样从内向外升起祖先元素上的处理器才能捕获到来自任何后代的事件配合始终指向最初触发元素的event.target就能实现一个处理器管一片元素。经典示例用单个处理器高亮表格单元格原文以八卦图Ba-Gua表格作为第一个演示一个 3×3 的表格任务是点击某个td单元格时高亮它。单元格可能有 9 个、99 个甚至 9999 个——逐个绑定onclick显然不可取。正确的做法是把统捕处理器挂到table元素上let selectedTd; table.onclick function(event) { let target event.target; // 事件实际发生在哪里 if (target.tagName ! TD) return; // 不是 TD那与我们无关 highlight(target); // 高亮它 }; function highlight(td) { if (selectedTd) { // 如果之前已有高亮的单元格先取消 selectedTd.classList.remove(highlight); } selectedTd td; selectedTd.classList.add(highlight); // 高亮新的 td }这段代码完全不关心表格里有多少个单元格随时可以动态增删td高亮功能依然正常工作因为它不依赖任何具体的单元格列表。在仓库的 bagua.view/index.html 中可以看到该示例的完整可运行实现其 HTML 结构与原文一致——每个td内部都嵌套了strong标签如strongNorthwest/strong。对应的样式定义在 bagua.view/bagua.css 中.highlight类将背景设为红色background: red九宫格各方位.nw、.n、.ne……则用不同颜色区分。嵌套元素陷阱事件不一定发生在 TD 上上面的初版代码有一个明显的缺陷点击可能并非发生在td上而是发生在它内部的嵌套标签如strong上。此时event.target的值就是那个strong而不是td如上图所示当点击落在strong上时事件会从这个最内层元素开始冒泡event.target始终指向strong处理器必须沿祖先链向上查找判断点击是否位于某个td之内。用 closest() 精准定位目标单元格改进版代码用elem.closest(selector)解决这个问题——它会返回沿祖先链向上找到的第一个匹配该选择器的元素table.onclick function(event) { let td event.target.closest(td); // (1) 向上找最近的 td if (!td) return; // (2) 不在任何 td 内直接返回 if (!table.contains(td)) return; // (3) 嵌套表格场景下确认是本表格的 td highlight(td); // (4) 高亮 };逐行解释event.target.closest(td)从被点击的元素可能是strong开始沿祖先链向上寻找最近的td如果event.target不在任何td内部closest返回null此时无事可做立即返回这一步处理嵌套表格的边界情况event.target可能确实是一个td但它属于外层/内层的另一个表格。table.contains(td)确保我们只处理当前这个表格自己的单元格确认无误后高亮该单元格。经过这层防护代码变得既快又稳而且依旧不在乎表格中td的总数量——这正是委托模式的核心收益。在标记中声明动作data-action 与菜单分发委托的另一大经典用途是动作分发。假设有一个菜单包含 Save、Load、Search 等按钮对应一个对象上的save、load、search方法。最自然的想法是给每个按钮单独绑定处理器但更优雅的解法是给整个菜单绑定一个处理器并给每个按钮加上data-action属性指明要调用的方法名button>div idmenu button>Counter: input typebutton value1>button>container.onclick function(event) { if (event.target.className ! remove-button) return; // 只关心 [x] 按钮 let pane event.target.closest(.pane); // 向上找到消息面板 pane.remove(); // 移除整条消息 };这里closest(.pane)再次派上用场[x]按钮位于.pane面板内部点击按钮即可定位并删除整个面板。样式见 messages.css。任务 2树形菜单的展开/折叠2-sliding-tree任务要求2-sliding-tree/task.md 指出只能有一个事件处理器使用委托并且点击节点标题之外的空白处不应触发任何动作。解答思路2-sliding-tree/solution.md分两步把每个树节点标题文本包装进span。这样span的宽度恰好等于文本宽度与直接点li不同既能在:hover时做 CSS 样式也能让点击精确地落在文本上在树根节点tree上挂一个处理器只对span标题上的点击做出反应。完整实现见 2-sliding-tree/solution.view/index.html// 把每个 li 的文本节点移入 spanspan 恰好包裹文本 for (let li of tree.querySelectorAll(li)) { let span document.createElement(span); li.prepend(span); span.append(span.nextSibling); // 把文本节点移进 span } // 在整棵树上捕获点击 tree.onclick function(event) { if (event.target.tagName ! SPAN) { return; // 点到了空白处/非标题区域忽略 } let childrenContainer event.target.parentNode.querySelector(ul); if (!childrenContainer) return; // 没有子节点 childrenContainer.hidden !childrenContainer.hidden; // 折叠/展开 }任务 3可点击排序的表格3-sortable-table在 3-sortable-table/solution.view/index.html 中委托让点击表头排序只用了一个grid.onclick处理器表头th带data-typenumber或data-typestring属性点击时根据th.cellIndex列索引与th.dataset.type列类型对tbody的行数组排序后重新追加grid.onclick function(e) { if (e.target.tagName ! TH) return; // 只处理表头点击 let th e.target; sortGrid(th.cellIndex, th.dataset.type); }; function sortGrid(colNum, type) { let tbody grid.querySelector(tbody); let rowsArray Array.from(tbody.rows); let compare; switch (type) { case number: compare (rowA, rowB) rowA.cells[colNum].innerHTML - rowB.cells[colNum].innerHTML; break; case string: compare (rowA, rowB) rowA.cells[colNum].innerHTML rowB.cells[colNum].innerHTML ? 1 : -1; break; } rowsArray.sort(compare); tbody.append(...rowsArray); // 按新顺序重排 DOM 行 }数字列与字符串列只需一个data-type属性即可切换比较逻辑之后新增列、新增行都无需改动 JS。任务 4行为式提示框4-behavior-tooltip任务要求4-behavior-tooltip/solution.md该任务解法说明在仓库中为空可结合 solution.view/index.html 的实现理解。这是行为模式的进阶应用元素通过data-tooltip提示内容属性声明自己的提示框行为document级处理器统一管理document.onmouseover function(event) { let target event.target; let tooltipHtml target.dataset.tooltip; // 该元素是否声明了提示框行为 if (!tooltipHtml) return; // 创建提示框元素并定位到元素上方居中 tooltipElem document.createElement(div); tooltipElem.className tooltip; tooltipElem.innerHTML tooltipHtml; document.body.append(tooltipElem); let coords target.getBoundingClientRect(); let left coords.left (target.offsetWidth - tooltipElem.offsetWidth) / 2; if (left 0) left 0; // 不越过窗口左边缘 let top coords.top - tooltipElem.offsetHeight - 5; if (top 0) { // 顶部放不下时改为显示在下方 top coords.top target.offsetHeight 5; } tooltipElem.style.left left px; tooltipElem.style.top top px; }; document.onmouseout function(e) { if (tooltipElem) { tooltipElem.remove(); tooltipElem null; } };该实现把提示框做成了纯粹的声明式能力给任意元素加上data-tooltip属性甚至支持HTMLbrtooltip这样的富文本页面滚动后提示框依然能正确定位全程无需为单个元素写任何 JS。总结算法、收益与局限事件委托的标准算法在容器上挂载一个处理器在处理器中检查源头元素event.target如果事件确实发生在感兴趣的元素内部就处理该事件。委托带来的收益简化初始化并节省内存无需给大量元素逐一添加处理器减少代码量增删元素时无需同步增删处理器天然支持 DOM 动态修改可以配合innerHTML等方式批量增删元素委托自动覆盖新内容。委托的局限必须知晓事件必须可冒泡有些事件本身不冒泡如focus委托无法生效此外底层处理器不应调用event.stopPropagation()阻断冒泡否则事件到不了容器层可能增加 CPU 负载容器级处理器会对容器内任意位置的事件都做出反应无论是否与它相关。不过这种开销通常可以忽略不计。总体而言事件委托是 DOM 事件处理中最值得掌握的模式之一它不仅用于大量相似元素的统一处理还支撑了data-action动作分发、data-counter/data-toggle-id/data-tooltip等声明式行为模式是构建可扩展、低耦合前端交互的基础设施。仓库中的 1-hide-message-delegate、2-sliding-tree、3-sortable-table、4-behavior-tooltip 四个任务提供了从入门到进阶的完整练习路径建议读者逐个对照task.md自行实现后再查看solution.view验证思路。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐Plate 富文本编辑器事件委托预算Event Delegation Budget实战指南Plate 富文本编辑器事件委托预算Event Delegation Budget实战指南 导读 本文围绕 plate 仓库性能规则文档 event del前端富文本UI组件使用事件委托实现嵌套>使用事件委托实现嵌套 data tooltip 提示框Modern JavaScript Tutorial 实战解析 本指南基于 Modern JavaScr文档/教程前端Capistrano 项目贡献指南从 Bug 上报到 Pull Request 的完整协作规范Capistrano 项目贡献指南从 Bug 上报到 Pull Request 的完整协作规范 导读 本文系统梳理 Capistrano基于 Ruby、Ra文档/教程前端上一篇腾讯混元7B开源256K上下文INT4量化重构AI部署范式下一篇Steamless彻底解放你的Steam游戏DRM限制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表