浏览器事件传播与 BOM:捕获、冒泡、History 和页面生命周期
本文目录
DOM 事件的常见传播路径包括捕获阶段、目标阶段和冒泡阶段。监听器默认在冒泡阶段运行;event.eventPhase 可以描述当前阶段,target 是最初触发节点,currentTarget 是当前监听器所属节点。事件委托正是利用冒泡,让父节点处理一组子元素交互。
stopPropagation 与 preventDefault
stopPropagation() 阻止事件继续向其他节点传播,但不取消链接、表单等浏览器默认动作;preventDefault() 取消可取消事件的默认动作,但事件仍会传播。被动监听器中调用 preventDefault 无效;stopImmediatePropagation() 还会阻止同一节点后续监听器,副作用更大,应慎用。
const menu = document.querySelector('[data-menu]');
menu?.addEventListener('click', (event) => {
if (!(event.target instanceof Element)) return;
const link = event.target.closest('a[href]');
if (!link || !menu.contains(link)) return;
if (link.dataset.confirm !== 'true') return;
if (!window.confirm('继续打开此页面?')) event.preventDefault();
});
除非要改变链接或表单默认行为,不要为了“阻止冒泡”随手拦截事件。优先用组件边界、明确选择器和数据契约解决重复响应。
监听器与页面加载
addEventListener 支持捕获、一次性、被动和 AbortSignal 等选项;使用统一的取消控制器可在页面离开或功能关闭时批量清理。DOMContentLoaded 表示 HTML 已解析且 defer/module 脚本已执行,不等待图片等资源;load 等全部依赖资源完成,通常不适合普通 DOM 初始化。
对当前页面一次性初始化优先使用 module 或 defer 脚本;组件框架用自身挂载/卸载生命周期。脚本可能在 DOMContentLoaded 之后动态加载时,先检查 document.readyState,避免错过事件。
History、URL 和导航状态
window.location 负责当前 URL 与完整页面导航;history.pushState/replaceState 只更新同源历史记录和地址,不会自动加载目标页面,也不自动触发 popstate。单页路由器需监听前进/后退并维护自己的状态。不要把密码、验证码、访问令牌或私密搜索内容放进 query/hash,因为它们可能进入浏览历史、日志和 Referer。
navigator.userAgent/platform 不应作为可靠的设备身份或安全条件;按能力检测和渐进增强设计。定时器不是精确调度器:页面后台可能被节流,退出时清理 setTimeout/setInterval。动画帧使用 requestAnimationFrame,实际倒计时用时间戳算剩余时间。
验收清单
- 点击嵌套图标时,委托逻辑是否找到正确的按钮?
- 是要取消默认行为,还是停止事件传播?是否确实需要?
- 监听器是否可被清理,动态页面是否处理了离开/返回?
- 初始化依赖 DOM 解析、图片加载,还是框架 hydration?
- URL 中是否包含任何凭据或不必要的个人数据?
参考:MDN Event.eventPhase、MDN preventDefault、MDN stopPropagation、MDN DOMContentLoaded、MDN History API、MDN Navigator.userAgent。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。