事件冒泡本身不慢,但dom深度超6层、父容器选错、scroll未设passive三者叠加会使低端机touchstart响应延迟超40ms;应控制嵌套层级、选用稳定最近父节点委托、scroll监听必加{passive:true}。

事件冒泡本身不慢,但结构失控时它会把性能问题放大十倍——DOM深度超6层、父容器选错、监听器挂太靠上,三者叠加会让低端机touchstart响应延迟直接飙到40ms以上。
DOM嵌套深度超6层触发冒泡路径过长
这不是JS执行慢,是浏览器得逐层向上遍历祖先节点,中间任意一层设了pointer-events: none或touch-action: none都可能中断路径。Chrome DevTools → Elements面板右键元素 → Show DOM properties能直接看到depth值。
- 真实卡点:
body > div > div > div > div > div > button这种结构在低端安卓机上,touchstart到handler执行稳定延迟42ms;三层语义化结构(如<main><nav><button></button></nav></main>)则压在8–12ms - 别为加class硬套
<div>,改用<code>display: contents抹除无意义包裹层(Safari 15.4+支持) <td>里禁止嵌套<code><div>:表格单元格内样式匹配成本高,混用flex极易触发重排 <h3>事件委托父容器选错导致判断开销反升</h3> <p>把监听器挂在<code>document或body上,看似省事,实则让每次点击都要从最底层冒泡到根,再由event.target层层回溯找目标——尤其当列表项超200条时,closest()调用本身就成了瓶颈。- 必须选离目标最近的**稳定父节点**,比如
<ul class="list"></ul>,而不是<div id="app"> <li>该父容器不能被<code>innerHTML或replaceChildren()频繁替换,否则监听器丢失 - 委托目标优先用
event.target.closest('.item-btn'),别手写while循环向上查父节点 -
scroll监听必须带{ passive: true }:window.addEventListener('scroll', handleScroll, { passive: true }) - 真要阻止滚动(如下拉刷新),改用
touchstart + touchmove组合,并显式传{ passive: false } - 别在
scroll回调里读getBoundingClientRect()或写el.style.top,这会触发强制同步布局
scroll/touchstart监听未设passive参数引发主线程阻塞
移动端浏览器默认把
scroll和touchstart当“可能调用preventDefault()”的事件,强制等JS执行完才滚动或响应——哪怕你根本没写preventDefault(),控制台也会报Unable to preventDefault inside passive event listener。真正难调的不是单个点,而是DOM深度超标+scroll没passive+委托父容器太靠上这三者同时存在——它们不单独报错,但合起来能让低端机FPS直接跌破25。先用WebDebugX打帧率日志,再按顺序砍掉最重的那根稻草。
- 必须选离目标最近的**稳定父节点**,比如











