先记住这个答案
once: true 让浏览器在监听器第一次执行后自动把它移除,适合“第一次点击后展示引导”或“只记录首次交互”。手动移除也能做到,但要保存原函数引用,并在正确时机调用 removeEventListener()。若组件在首次事件前就被销毁,once 不会替你提前清理,这时可用 AbortController 的 signal 或显式移除。
once首次调用后自动移除- 手动移除要用同一函数引用
- 监听器未触发前仍可能需要清理
signal可统一取消监听
首次点击只提示一次
新功能首次使用时显示说明,后续点击走原本业务。once 能把“一次”直接声明在注册监听器处,避免回调尾部遗漏清理。
const button = document.querySelector('#export')
let shown = 0
// 首次点击后,浏览器会自动移除这一条监听器
button.addEventListener('click', () => {
shown += 1
console.log('导出说明展示次数:', shown) // 只会输出一次
}, { once: true })第一次点击后,这个监听器会自动退场。按钮上其他 click 监听器不受影响,也不会因为这个配置失去后续点击能力。
如果 showGuide() 自身异步执行,once 仍只控制监听器是否再次被调用,不会自动取消已经开始的异步任务。
手动移除需要引用相同
下面两次写出来的箭头函数即使代码相同,也不是同一个函数对象。removeEventListener("click", () => showGuide()) 无法移除先前注册的箭头函数。要手动清理,应把处理函数保存为变量。
还要注意移除匹配时的捕获配置。多个相同类型监听器若分别在捕获与冒泡阶段注册,清理时应对应阶段。页面或组件销毁前尚未触发的 once 监听器,可用 signal 一次取消,避免长期挂在仍存活的目标上。
例如弹窗打开后只等用户按下一次 Escape,若用户先点关闭按钮,那个键盘监听就永远等不到 Escape。把监听注册时的 signal 与弹窗生命周期绑定,关闭时调用 abort(),才能覆盖“事件发生”和“组件先消失”两条结束路径。once 解决次数,signal 解决提前清理,职责不同。
once 的“一次”是监听器被调用一次,不是业务成功一次。回调里校验失败、提前 return,浏览器也已移除监听器。需要“直到输入合法才停止监听”时,应自己保存处理函数并在成功分支调用 removeEventListener()。
容易答错的地方
- 以为
once会让事件只发生一次 - 它只让这一条监听器执行一次,事件本身和其他监听器照常运行。
- 用新箭头函数尝试手动移除
- 函数引用不同,旧监听器不会被移除。
面试官还会怎么问?
监听器从未触发,once 会自动清理吗?
不会仅因时间过去就退场;目标生命周期结束或主动移除时才结束等待。
once 和 signal 能一起用吗?
可以。触发一次后自动移除,或在触发前调用 abort() 主动取消。
once 监听器里抛错会怎样?
它仍是一次性监听器;不应把“异常后重试”寄托在下一次同一监听器调用上。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。