浏览器、网络与安全 · KNOWLEDGE CHAPTER
K17

DOM 事件与观察器

事件描述某次发生的交互,观察器描述某类状态的变化。正确设计先选信号,再界定传播范围、异步回调归属与清理,最后检查自己的写入会不会反过来制造同一信号。

浏览器、网络与安全2 道章节练习4 道主练关联

核心模型

事件描述某次发生的交互,观察器描述某类状态的变化。正确设计先选信号,再界定传播范围、异步回调归属与清理,最后检查自己的写入会不会反过来制造同一信号。

原理与机制

事件路径是组件边界的一部分

事件通常沿捕获、目标、冒泡传播,但具体事件是否冒泡、能否取消需分别判断。事件委托在稳定祖先监听,从 target 找业务节点,并确认属于当前容器,不能把嵌套组件的按钮也纳入自己管理。currentTarget 表示当前监听器节点,stopPropagation 不等于 preventDefault,也不阻止同节点其他监听器。Shadow DOM 会重定向 target,跨边界还要看 composed;composedPath 只暴露调用方可见路径,闭合影子树不会因此变透明。

触控手势在开始前就有约定

passive 表达监听器不取消默认行为,让浏览器不必为滚动等待它;在被动监听中 preventDefault 无效。touch-action 预先声明浏览器允许的手势,如横向控件保留 pan-y 与缩放;开始后再改属性不会重新决定当前手势。Pointer Events 统一鼠标、笔和触控,pointer capture 保证指针移出后仍可接收相应事件,却不自动抢占滚动。状态按 pointerId 归属,并在 up、cancel、lostpointercapture 与卸载时完整清理。

观察器应匹配真正的问题

MutationObserver 观察 DOM 结构、属性与文本,不是尺寸或可见性 API;ResizeObserver 观察元素尺寸;IntersectionObserver 异步观察目标与根区域的相交。基础相交不等于人眼真正看见,遮挡、透明与注意力都不在简单判断里。观察回调不是同一执行阶段,不能混用 nextTick 或固定延时去猜。无限列表应选择正确滚动 root、合理 rootMargin 与阈值,同时保留按钮或分页以应对键盘和故障路径。

反馈必须收敛,加载必须前进

ResizeObserver 回调把宽度改成当前宽度加一会永远触发自己;放进 rAF 只是把循环延长到下一帧。优先让 CSS 决定布局,观察器更新独立的绘图缓冲,并只写真正改变的值。无限列表用 loading、hasMore、游标和查询版本防重;哨兵一直可见不会自动持续触发,因此每页布局完成后可串行检查是否需要补足。遇到空页、重复游标、错误或自动补足预算耗尽就停,避免把坏接口变成无限请求。

公共事件让封装保持稳定

影子树内部的业务事件若需要向祖先传播,通常显式设置 bubbles 与 composed,并用 detail 携带稳定记录 ID;cancelable 则另行决定调用方能否否决默认业务动作。从宿主自身派发时,应按实际传播路径判断所需标志,不能机械照抄。外部页面因此只依赖公开协议,内部从开放影子树改成封闭模式也不必修改父级选择器。验收一次操作只有一次通知,且阻止默认动作不会意外关闭无关的传播链。

最小示例

javascript
javascript
const list = document.querySelector('[data-list]');
const controller = new AbortController();
list.addEventListener('click', event => {
  if (!(event.target instanceof Element)) return;
  const button = event.target.closest('button[data-id]');
  if (!button || button.closest('[data-list]') !== list) return;
  console.log('打开记录', button.dataset.id);
}, { signal: controller.signal });
// 新增 button 无需重新绑定;组件销毁:
// controller.abort();

点击按钮中的 span 仍能找到按钮,动态新增行也沿同一委托处理;嵌套的 data-list 则被所有权检查排除。原生 button 已处理键盘激活,不必再用 div 补一套。此示例针对普通 DOM,Shadow DOM 内部交互应发布稳定公共事件,不让父级依赖内部选择器。

环境:现代浏览器;页面已有 data-list 容器及带 data-id 的原生 button;这里只演示普通 DOM 委托。

核验:已静态审查示例与本章机制一致;未运行此示例,未进行框架或浏览器集成测试。观察结果为待复现的教学预期。

边界与取舍

边界

  • 事件捕获与指针捕获是不同机制;后者不会自动阻止浏览器接管允许的滚动手势。
  • rAF 不能修复不收敛的尺寸关系;observer 回调内的写入也可能制造新的观察记录。
  • 哨兵持续相交不保证不断回调;通过反复 observe 强制刷新而无状态边界会造成请求风暴。

取舍

  • 委托减少监听与动态绑定成本,但增加所有权判断;封装组件应优先明确事件接口。
  • 提前预加载改善到达底部体验,却可能浪费流量;rootMargin 与自动补足预算应依据网络和内容密度测量。

口述示范

我先区分交互事件、尺寸变化、DOM 变化和相交状态,再选择对应 API。事件讲传播、默认行为与组件边界;观察器讲异步、收敛与清理。拖拽和无限列表的可靠性来自明确状态机,不能靠某个 API 名称自动获得。

章节练习

练习 1

横向滑块在手机上丢拖拽或锁住整页,如何拆解原因?

展开检查点
  • 配置符合需求的 touch-action,保留纵向滚动和缩放。
  • pointerId 配对捕获,处理 cancel 与丢失捕获。
  • 测试斜拖、多指、移出、卸载与键盘调整。
练习 2

列表初始不足一屏停止加载,同时图表出现 ResizeObserver 循环警告,分别修复。

展开检查点
  • 列表布局后有界补足,保证游标前进与请求串行。
  • 图表容器由 CSS 定尺寸,画布缓冲不反向撑大容器。
  • 只在尺寸变化时重设缓冲,卸载 disconnect 并取消请求。

参考来源与核验边界

以现代 DOM、Pointer Events 与观察器 API 为基线;可见性增强选项、设备像素观察与 WebView 行为须单独检测。

内容版本:2026-10-04.2 · 题目和来源 ID 保持原样,本站不将面经标签解释为企业官方出题或高频保证。