浏览器事件按捕获→目标→冒泡三阶段传递;默认监听在冒泡阶段;stopPropagation仅阻断当前阶段传递;target是触发事件的最深元素,currentTarget是绑定监听器的元素。

事件是如何被浏览器捕获和传递的
JavaScript 事件不是一触发就直接跑到你的 addEventListener 回调里去的,中间要经过“捕获 → 目标 → 冒泡”三个阶段。默认情况下,你绑定的事件监听器都在冒泡阶段执行——这也是为什么 document.addEventListener('click', ...) 能捕获子元素的点击。
容易踩的坑:
- 误以为
event.stopPropagation()能阻止所有后续监听器执行——它只阻止当前阶段(比如冒泡)向上传递,同阶段已注册的其他监听器仍会运行 - 用
useCapture参数设为true开启捕获阶段监听时,父元素监听器会比子元素先触发,和直觉相反 -
target和currentTarget不是同一个东西:target是真正被点击的最深节点(比如按钮里的),currentTarget才是你绑定事件的那个元素(比如外层)
怎样正确绑定和解绑事件避免内存泄漏
动态创建的元素、单页应用中频繁切换视图时,不手动解绑事件会导致监听器堆积,尤其在 IE 或旧版移动端 WebKit 中可能引发崩溃。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 优先使用事件委托:给父容器绑定一次
addEventListener('click', handler),在handler里用event.target.matches('.btn-delete')判断是否要点的元素,而不是给每个按钮单独绑定 - 解绑必须用完全相同的函数引用:
el.addEventListener('click', fn)对应el.removeEventListener('click', fn);用匿名函数或箭头函数就永远解不掉 - 现代写法可配合
AbortController:const controller = new AbortController();
element.addEventListener('click', handler, { signal: controller.signal });
// 后续调用 controller.abort() 即可批量解绑
click 和 touchstart 在移动端为什么经常失效或延迟
这不是 JS 的问题,而是浏览器为了兼容双击缩放而引入的 300ms 延迟——iOS Safari 和旧版 Android 浏览器会在 touchstart 后等这么长时间,确认用户没打算双击,才派发 click。
解决方案取决于场景:
- 只要响应快:直接监听
touchstart,但注意它不会自动阻止默认行为,也不像click那样有 :active 伪类反馈 - 需要保持 click 语义(如表单提交):用
fastclick库或 CSS 添加touch-action: manipulation(对大多数 tap 场景有效) - 混合输入设备(触屏+鼠标):监听
pointerdown,它统一了 mouse/touch/pen 事件,且无 300ms 延迟,但需检查兼容性(IE10+、现代 Chrome/Firefox/Safari 都支持)
preventDefault 什么时候该用、什么时候不该用
event.preventDefault() 的作用是取消浏览器对该事件的**默认行为**,不是阻止事件传播。很多人把它和 stopPropagation 混用,结果导致页面无法滚动、链接打不开、空格键不能翻页等意外后果。
关键判断点:
- 该用:
submit表单提交前校验失败;keydown中拦截回车键防止换行(如搜索框);touchmove中禁止页面滚动(如轮播图手势) - 不该用:
click在普通按钮上随意调用——除非你明确接管了后续逻辑;scroll事件里调用会直接锁死滚动条 - 特别注意:
passive: true是addEventListener的一个选项,表示“我绝不会在touchstart或wheel里调用preventDefault”,浏览器因此能提前优化滚动性能;一旦设了 passive,再在回调里调preventDefault就会报警告甚至被忽略











