先记住这个答案
event.defaultPrevented 表示这次事件的默认行为是否已经被成功取消。前面的监听器调用 preventDefault() 后,后面的监听器可读取到 true,但只有 event.cancelable 为真且调用有效才成立。它不告诉你是谁取消的,也不代表事件传播停止;stopPropagation() 解决的是传播路径,职责不同。
defaultPrevented是只读布尔值preventDefault()只对可取消事件有效- 它不表示传播已停止
- 读取结果取决于监听器执行时机
嵌套组件尊重已处理的操作
父容器有统一菜单逻辑,子组件在某些情况下想阻止默认打开动作。父监听器可在自己执行时先看 defaultPrevented,避免重复处理已明确取消的行为。
const menu = document.querySelector('#menu')
menu.querySelector('[data-cancel-menu]').addEventListener('click', event => {
event.preventDefault() // 子项先取消这次可取消的 click
})
menu.addEventListener('click', event => {
if (event.defaultPrevented) return
console.log('执行菜单动作', event.target.textContent)
})点击带 data-cancel-menu 的子项时,事件先到达子项,再冒泡到菜单;父级因此读到 defaultPrevented === true。读取结果只反映此前已经执行的监听器。若另一个监听器稍后才调用 preventDefault(),当前回调不可能提前看到未来状态。
还要区分浏览器默认行为和业务回调。取消链接导航不会自动阻止其他 JavaScript 监听器执行,业务监听器是否退出,要自己检查状态或约定处理方式。
可取消、被动和传播
许多事件有默认行为可以取消,但不是所有事件都可取消。先检查 cancelable,再判断 defaultPrevented,能避免“我调用过 preventDefault(),怎么还是 false”的疑惑。被动监听器还不能有效取消默认行为。
stopPropagation() 只阻止事件继续传播,不会把 defaultPrevented 设为真;stopImmediatePropagation() 还会阻止同目标后续监听器,但仍与取消默认行为不同。
在多个组件共享一次点击时,defaultPrevented 可以作为“前面有人已经否决默认动作”的约定,但它不等于通用的“业务已处理”标志。子组件可能只阻止链接跳转,却仍希望父组件记录点击;父组件若一看到 true 就退出,埋点也会被跳过。具体哪些处理应退出,需要双方约定清楚。
监听器的执行次序也影响读取结果。子项的冒泡监听先于父项运行,所以父项能看到子项取消;父项的捕获监听在子项之前运行,那时还看不到后续取消。需要在父级统一兜底时,应把监听阶段算进设计。
容易答错的地方
- 把
defaultPrevented当成传播停止标志 - 停止传播不会自动取消默认行为,两者要分别判断。
- 在不可取消事件上调用
preventDefault()就以为成功 cancelable: false时不会产生期望的取消状态。
面试官还会怎么问?
怎样知道事件能否取消?
读取 event.cancelable。
调用 preventDefault() 会停止其他监听器吗?
不会。其他监听器仍可运行,除非另行控制传播或在回调里检查状态。
为什么滚动监听里取消无效?
可能注册了被动监听器,或该事件本身不可取消;要分别核查。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。