系列文章目录《JavaScript 基础与进阶笔记》前期偏基础巩固与常见面试点后续进入闭包、异步、工程化等进阶主题第 01 篇数据类型与类型判断第 02 篇变量声明与作用域第 03 篇闭包与高阶函数第 04 篇函数工厂第 05 篇this 指向与绑定第 06 篇原型与原型链第 07 篇类与继承第 08 篇JS 执行机制与异步队列第 09 篇数组常用方法第 10 篇字符串算法第 11 篇常见手写题合集上第 12 篇常见手写题合集下第 13 篇Promise 与 async/await第 14 篇数据结构基础第 15 篇垃圾回收与内存第 16 篇DOM 基础全面解析第 17 篇DOM 性能与渲染第 18 篇DOM 交互补充本文文章目录系列文章目录前言一、事件流捕获与冒泡二、target 与 currentTarget三、事件委托面试重点3.1 是什么3.2 为什么用3.3 局限四、不冒泡时的替代五、IntersectionObserver可见性与 loadinglazy 选型六、requestAnimationFramerAF与 setTimeout、微任务的分工七、了解即可一笔带过八、易混淆点归纳九、思考与练习总结前言DOM 主线分三篇收尾第 16 篇讲节点与 API第 17 篇讲渲染与性能本篇补交互层——用户点击、滚动、元素进出视口时 JS 怎么响应。面试最常问的是事件委托工程里还会用到IntersectionObserver懒加载、加载更多和requestAnimationFrame动画/滚动。CustomEvent、passive等知道存在即可不必单独成篇。一、事件流捕获与冒泡DOM 事件传播捕获外→内→ 目标 → 冒泡内→外。默认addEventListener在冒泡阶段触发。outer.addEventListener(click,()console.log(outer 冒泡));outer.addEventListener(click,()console.log(outer 捕获),true);btn.addEventListener(click,()console.log(btn));// 点击 btnouter 捕获 → btn → outer 冒泡日常写业务冒泡阶段足够捕获多用于拦截或框架内部了解即可。二、target与currentTarget属性含义event.target实际触发事件的元素最内层可能是文本节点event.currentTarget当前执行监听器的元素addEventListener绑定的那个委托时二者不同绑在父级#list上currentTarget始终是#listtarget是具体子元素。list.addEventListener(click,(e){constlie.target.closest(li);if(!li||!list.contains(li))return;console.log(点到 li:,li.dataset.id);});closest(selector)从target向上找匹配祖先避免点到span/文本节点时对不上li。三、事件委托面试重点3.1 是什么在祖先元素上绑一个监听器利用冒泡处理多个子孙含后来插入的节点的同类事件。3.2 为什么用动态列表新增/删除项不用反复addEventListener/removeEventListener。监听器更少内存与注册成本更低第 17 篇。逻辑集中列表、表格、菜单等结构清晰。constlistdocument.querySelector(#list);list.addEventListener(click,(e){constiteme.target.closest(li);if(!item)return;item.classList.toggle(active);});document.querySelector(#add).addEventListener(click,(){constlidocument.createElement(li);li.textContent新项;list.appendChild(li);// 无需再绑 click});3.3 局限依赖冒泡focus、mouseenter等不冒泡不能这样委托。需要closest过滤避免误触嵌套结构。四、不冒泡时的替代不冒泡可冒泡替代focus/blurfocusin/focusoutmouseenter/mouseleavemouseover/mouseout会因子元素频繁触发form.addEventListener(focusin,(e){if(e.target.matches(input))e.target.classList.add(focused);});scroll也不冒泡需绑在滚动容器本身。五、IntersectionObserver可见性异步观察元素与**视口或 root 容器**的交叉状态常用于图片懒加载进入视口再设src无限滚动底部哨兵进入视口加载下一页曝光统计元素可见比例达标上报constionewIntersectionObserver((entries){entries.forEach((entry){if(!entry.isIntersecting)return;constimgentry.target;img.srcimg.dataset.src;io.unobserve(img);});},{rootMargin:100px,threshold:0.01});document.querySelectorAll(img[data-src]).forEach((img)io.observe(img));选项作用知道即可root观察根默认视口rootMargin扩大/缩小触发区域如提前 100px 加载threshold可见比例 01 触发回调与loadinglazy选型loadinglazyIntersectionObserver成本原生零 JS需写逻辑控制浏览器决定rootMargin、回调自定义场景普通img懒加载无限滚动哨兵、复杂曝光建议纯图片懒加载优先原生加载更多、埋点用 IO。六、requestAnimationFramerAF在下一次重绘前执行回调与显示器刷新率对齐约 60fps适合动画循环、滚动中更新 UI。lettickingfalse;window.addEventListener(scroll,(){if(ticking)return;tickingtrue;requestAnimationFrame((){updateHighlight();// 读 scrollTop、改 class 等tickingfalse;});});与setTimeout、微任务的分工机制典型用途微任务Promise.then异步结果、DOM 更新调度第 08 篇setTimeout延迟、防抖定时rAF视觉相关、跟帧动画/滚动rAF不是精确定时器后台标签页可能暂停或降频。精确计时应使用performance.now() 时间差而非假设每帧 16ms。动画属性优先transform/opacity第 17 篇在 rAF 里改它们更顺滑。七、了解即可一笔带过passive: true告诉浏览器不会preventDefault滚动更流畅要阻止滚动需passive: false。once: true监听一次后自动移除。CustomEventDOM 节点上派发自定义事件模块解耦复杂场景可用 EventBus第 11 篇。React / Vue框架在根或元素上统一处理事件原生addEventListener与框架onClick勿重复绑卸载时记得清理。八、易混淆点归纳委托靠冒泡focus要用focusin。target≠currentTarget委托看targetclosest。IO 不是 scroll 事件不阻塞主线程滚动回调异步触发。rAF ≠ 微任务别用 rAF 替代Promise.then的语义。loadinglazy与 IO互补不是二选一排斥。九、思考与练习1.动态 Todo 列表为何推荐委托而不是每项onclick解析新增项不用绑事件监听器O(1)维护简单。2.点击li内文字target可能是谁如何拿到li解析可能是Text 节点或内层元素e.target.closest(li)。3.图片首屏外懒加载原生属性与 IO 如何选解析简单img loadinglazy要提前加载距离或加载更多哨兵用IO。4.滚动监听里直接做重 DOM 操作为何卡rAF 起什么作用解析scroll 触发极频繁rAF合并到每帧一次且对齐重绘。5.form.addEventListener(focus, ...)能委托到所有 input 吗解析不能focus不冒泡用focusin。总结事件捕获/冒泡委托 父级监听 冒泡 closest适合动态列表。可见性IntersectionObserver做懒加载、无限滚动简单图片可用loadinglazy。rAF动画/滚动对齐帧与微任务、定时器分工不同。DOM 三篇16 基础 / 17 性能 / 18 交互至此收束。下一阶段进入CSS 布局居中、BFC、Flex/Grid 等系列后续篇目。
DOM 交互补充:事件委托、可见性与 rAF
发布时间:2026/5/26 15:44:30
系列文章目录《JavaScript 基础与进阶笔记》前期偏基础巩固与常见面试点后续进入闭包、异步、工程化等进阶主题第 01 篇数据类型与类型判断第 02 篇变量声明与作用域第 03 篇闭包与高阶函数第 04 篇函数工厂第 05 篇this 指向与绑定第 06 篇原型与原型链第 07 篇类与继承第 08 篇JS 执行机制与异步队列第 09 篇数组常用方法第 10 篇字符串算法第 11 篇常见手写题合集上第 12 篇常见手写题合集下第 13 篇Promise 与 async/await第 14 篇数据结构基础第 15 篇垃圾回收与内存第 16 篇DOM 基础全面解析第 17 篇DOM 性能与渲染第 18 篇DOM 交互补充本文文章目录系列文章目录前言一、事件流捕获与冒泡二、target 与 currentTarget三、事件委托面试重点3.1 是什么3.2 为什么用3.3 局限四、不冒泡时的替代五、IntersectionObserver可见性与 loadinglazy 选型六、requestAnimationFramerAF与 setTimeout、微任务的分工七、了解即可一笔带过八、易混淆点归纳九、思考与练习总结前言DOM 主线分三篇收尾第 16 篇讲节点与 API第 17 篇讲渲染与性能本篇补交互层——用户点击、滚动、元素进出视口时 JS 怎么响应。面试最常问的是事件委托工程里还会用到IntersectionObserver懒加载、加载更多和requestAnimationFrame动画/滚动。CustomEvent、passive等知道存在即可不必单独成篇。一、事件流捕获与冒泡DOM 事件传播捕获外→内→ 目标 → 冒泡内→外。默认addEventListener在冒泡阶段触发。outer.addEventListener(click,()console.log(outer 冒泡));outer.addEventListener(click,()console.log(outer 捕获),true);btn.addEventListener(click,()console.log(btn));// 点击 btnouter 捕获 → btn → outer 冒泡日常写业务冒泡阶段足够捕获多用于拦截或框架内部了解即可。二、target与currentTarget属性含义event.target实际触发事件的元素最内层可能是文本节点event.currentTarget当前执行监听器的元素addEventListener绑定的那个委托时二者不同绑在父级#list上currentTarget始终是#listtarget是具体子元素。list.addEventListener(click,(e){constlie.target.closest(li);if(!li||!list.contains(li))return;console.log(点到 li:,li.dataset.id);});closest(selector)从target向上找匹配祖先避免点到span/文本节点时对不上li。三、事件委托面试重点3.1 是什么在祖先元素上绑一个监听器利用冒泡处理多个子孙含后来插入的节点的同类事件。3.2 为什么用动态列表新增/删除项不用反复addEventListener/removeEventListener。监听器更少内存与注册成本更低第 17 篇。逻辑集中列表、表格、菜单等结构清晰。constlistdocument.querySelector(#list);list.addEventListener(click,(e){constiteme.target.closest(li);if(!item)return;item.classList.toggle(active);});document.querySelector(#add).addEventListener(click,(){constlidocument.createElement(li);li.textContent新项;list.appendChild(li);// 无需再绑 click});3.3 局限依赖冒泡focus、mouseenter等不冒泡不能这样委托。需要closest过滤避免误触嵌套结构。四、不冒泡时的替代不冒泡可冒泡替代focus/blurfocusin/focusoutmouseenter/mouseleavemouseover/mouseout会因子元素频繁触发form.addEventListener(focusin,(e){if(e.target.matches(input))e.target.classList.add(focused);});scroll也不冒泡需绑在滚动容器本身。五、IntersectionObserver可见性异步观察元素与**视口或 root 容器**的交叉状态常用于图片懒加载进入视口再设src无限滚动底部哨兵进入视口加载下一页曝光统计元素可见比例达标上报constionewIntersectionObserver((entries){entries.forEach((entry){if(!entry.isIntersecting)return;constimgentry.target;img.srcimg.dataset.src;io.unobserve(img);});},{rootMargin:100px,threshold:0.01});document.querySelectorAll(img[data-src]).forEach((img)io.observe(img));选项作用知道即可root观察根默认视口rootMargin扩大/缩小触发区域如提前 100px 加载threshold可见比例 01 触发回调与loadinglazy选型loadinglazyIntersectionObserver成本原生零 JS需写逻辑控制浏览器决定rootMargin、回调自定义场景普通img懒加载无限滚动哨兵、复杂曝光建议纯图片懒加载优先原生加载更多、埋点用 IO。六、requestAnimationFramerAF在下一次重绘前执行回调与显示器刷新率对齐约 60fps适合动画循环、滚动中更新 UI。lettickingfalse;window.addEventListener(scroll,(){if(ticking)return;tickingtrue;requestAnimationFrame((){updateHighlight();// 读 scrollTop、改 class 等tickingfalse;});});与setTimeout、微任务的分工机制典型用途微任务Promise.then异步结果、DOM 更新调度第 08 篇setTimeout延迟、防抖定时rAF视觉相关、跟帧动画/滚动rAF不是精确定时器后台标签页可能暂停或降频。精确计时应使用performance.now() 时间差而非假设每帧 16ms。动画属性优先transform/opacity第 17 篇在 rAF 里改它们更顺滑。七、了解即可一笔带过passive: true告诉浏览器不会preventDefault滚动更流畅要阻止滚动需passive: false。once: true监听一次后自动移除。CustomEventDOM 节点上派发自定义事件模块解耦复杂场景可用 EventBus第 11 篇。React / Vue框架在根或元素上统一处理事件原生addEventListener与框架onClick勿重复绑卸载时记得清理。八、易混淆点归纳委托靠冒泡focus要用focusin。target≠currentTarget委托看targetclosest。IO 不是 scroll 事件不阻塞主线程滚动回调异步触发。rAF ≠ 微任务别用 rAF 替代Promise.then的语义。loadinglazy与 IO互补不是二选一排斥。九、思考与练习1.动态 Todo 列表为何推荐委托而不是每项onclick解析新增项不用绑事件监听器O(1)维护简单。2.点击li内文字target可能是谁如何拿到li解析可能是Text 节点或内层元素e.target.closest(li)。3.图片首屏外懒加载原生属性与 IO 如何选解析简单img loadinglazy要提前加载距离或加载更多哨兵用IO。4.滚动监听里直接做重 DOM 操作为何卡rAF 起什么作用解析scroll 触发极频繁rAF合并到每帧一次且对齐重绘。5.form.addEventListener(focus, ...)能委托到所有 input 吗解析不能focus不冒泡用focusin。总结事件捕获/冒泡委托 父级监听 冒泡 closest适合动态列表。可见性IntersectionObserver做懒加载、无限滚动简单图片可用loadinglazy。rAF动画/滚动对齐帧与微任务、定时器分工不同。DOM 三篇16 基础 / 17 性能 / 18 交互至此收束。下一阶段进入CSS 布局居中、BFC、Flex/Grid 等系列后续篇目。