01事件流深潜
06 章学过「怎么绑事件」。这一课回答更扎心的问题:
为什么点了两次?为什么阻止默认没用?列表项动态添加后点不动了?
把事件流三阶段、委托、once/passive 一次吃透——这三件事决定了你写的交互稳不稳。
一、事件流三阶段(必须能画出来)
1捕获 Capture
从 window → document → … → 目标父级。默认不触发你绑定的监听(除非第三参为 true)。
2目标 Target
事件到达真正被点的元素。event.target 就是它。
3冒泡 Bubble
从目标向上传回 window。你平时绑的 addEventListener 就在这阶段跑。
外层 outer
中层 mid
点击按钮看事件流顺序
二、target vs currentTarget
| 属性 | 是谁 | 典型用途 |
|---|---|---|
event.target |
真正被点的元素 | 事件委托:知道点的是哪个 li |
event.currentTarget |
绑监听的那个元素 | 在父级监听时拿父级自己 |
this(普通函数) |
同 currentTarget |
箭头函数里没有,用 currentTarget |
this 是父容器,不是被点的子项。
要拿子项请用 e.target 或 e.target.closest('.item')。三、事件委托:动态列表的正解
给 1000 个 li 各绑 click,内存和绑定成本都高;
后来 appendChild 新增的项还点不动。
委托(delegation)只在父容器绑一次,靠 target 判断点到谁。
- 列表项 1(点我)
点列表项 / 添加后再点
四、stopPropagation vs preventDefault
| 方法 | 干什么 | 不干什么 | 常见误用 |
|---|---|---|---|
stopPropagation() |
不再向上/下传播 | 不阻止默认行为 | 想拦表单提交却只写了它 |
preventDefault() |
阻止默认行为(跳转/提交/选中) | 不阻止冒泡 | 想拦父级监听却只写了它 |
stopImmediatePropagation() |
连同元素后续监听也不跑 | — | 误以为和 stopPropagation 一样 |
return false(jQuery) |
两者都干 | 原生 JS 里无效 | 从 jQuery 迁过来踩坑 |
外层 box(监听冒泡)
点我这个链接点链接看三种拦截效果
五、once 与 passive:现代监听选项
passive: true 之后再调
preventDefault() 会被忽略并告警。
滚动 / 触摸相关监听尽量标 passive,列表滚动会明显更跟手。六、常见问题 · 自问自答
为什么我的点击回调跑了两次?
三种常见原因:① 父子都绑了 click,冒泡各跑一次 → 用 stopPropagation 或只在父级委托;
② 同一函数被 addEventListener 绑了两次(React 热更新 / 重复初始化)→ 用 {once:true} 或先 remove;
③ 同时绑了捕获和冒泡(第三参 true/false 各一次)→ 检查选项对象。
为什么 preventDefault() 没拦住表单提交 / 链接跳转?
① 监听绑在了 submit 却写了 click 的 preventDefault——表单要用 submit 事件;
② 监听绑太晚,原生提交已经发生——保证 type="submit" 按钮在外层 form 内,并在 submit 上拦;
③ 监听选项写了 passive: true,preventDefault 被忽略。
为什么动态 appendChild 的按钮点了没反应?
事件是绑在「当时已存在」的元素上的,新节点身上没有监听。两条路:
① 创建后立刻 btn.addEventListener;
②(推荐)改事件委托,只绑稳定的父容器。
e.target 和 this 在委托里怎么选?
this / e.currentTarget = 绑监听的父级;
e.target = 真正被点的元素(可能是图标 span)。
要稳定拿到「行 / 卡片」,用 e.target.closest('.item'),比 e.target 更抗嵌套。
七、排错清单(打印不出来时按顺序查)
- 事件类型拼写:
click≠Click;表单是submit不是click。 - 元素是否已存在:脚本是否在
defer/DOMContentLoaded之后跑。 - 是否被
stopPropagation截胡:临时在window上绑一个 log 看有没有到顶。 - 是否重复绑定 / 已
removeEventListener(引用必须同一个函数)。 - 遮挡:透明元素 / 伪元素挡住了点击(DevTools → 选中元素看 hit target)。
八、本章小结
- 三阶段:捕获向下 → 目标 → 冒泡向上;默认监听在冒泡。
target是被点的;currentTarget/this是绑监听的。- 动态列表用事件委托 +
closest。 preventDefault管默认行为;stopPropagation管传播——别混。{ once, passive, capture }是现代默认写法。
submit 时 preventDefault,把数据 console.table 出来。