addEventListener 第三个参数

addEventListener 第三个参数的两种形式及使用场景

问题

addEventListener 函数的第三个参数有什么作用?

解答

addEventListener 的第三个参数有两种形式:布尔值配置对象

基本语法

element.addEventListener(type, listener, useCapture)
element.addEventListener(type, listener, options)

事件流回顾

理解第三个参数前,需要了解事件流的三个阶段:

捕获阶段 → 目标阶段 → 冒泡阶段
(从外到内)   (目标元素)   (从内到外)

布尔值形式

// 第三个参数为布尔值,表示是否在捕获阶段触发
// 默认为 false(冒泡阶段触发)

const outer = document.getElementById('outer')
const inner = document.getElementById('inner')

// 冒泡阶段触发(默认)
outer.addEventListener('click', () => {
  console.log('outer - 冒泡')
}, false)

// 捕获阶段触发
outer.addEventListener('click', () => {
  console.log('outer - 捕获')
}, true)

inner.addEventListener('click', () => {
  console.log('inner')
})

// 点击 inner 时输出顺序:
// outer - 捕获
// inner
// outer - 冒泡

配置对象形式

element.addEventListener('click', handler, {
  capture: false,  // 是否在捕获阶段触发,默认 false
  once: false,     // 是否只触发一次,默认 false
  passive: false,  // 是否为被动监听器,默认 false
  signal: null     // AbortSignal,用于移除监听器
})

once - 只触发一次

const btn = document.getElementById('btn')

btn.addEventListener('click', () => {
  console.log('只会触发一次')
}, { once: true })

// 等价于
btn.addEventListener('click', function handler() {
  console.log('只会触发一次')
  btn.removeEventListener('click', handler)
})

passive - 被动监听器

// passive: true 告诉浏览器不会调用 preventDefault()
// 浏览器可以立即滚动,不用等待 JS 执行完毕
// 常用于优化滚动性能

document.addEventListener('touchstart', (e) => {
  // 这里不能调用 e.preventDefault()
  console.log('touch start')
}, { passive: true })

// 滚动事件优化
window.addEventListener('scroll', () => {
  // 处理滚动
}, { passive: true })

signal - 使用 AbortController 移除监听

const controller = new AbortController()

document.addEventListener('click', () => {
  console.log('clicked')
}, { signal: controller.signal })

// 移除监听器
controller.abort()

// 适合批量移除多个监听器
const controller2 = new AbortController()

element.addEventListener('click', handler1, { signal: controller2.signal })
element.addEventListener('keydown', handler2, { signal: controller2.signal })
element.addEventListener('scroll', handler3, { signal: controller2.signal })

// 一次性移除所有监听器
controller2.abort()

完整示例

<!DOCTYPE html>
<html>
<body>
  <div id="outer" style="padding: 50px; background: #eee;">
    outer
    <div id="inner" style="padding: 30px; background: #ddd;">
      inner
    </div>
  </div>

  <script>
    const outer = document.getElementById('outer')
    const inner = document.getElementById('inner')

    // 捕获阶段
    outer.addEventListener('click', () => {
      console.log('1. outer 捕获')
    }, { capture: true })

    // 冒泡阶段
    outer.addEventListener('click', () => {
      console.log('4. outer 冒泡')
    }, { capture: false })

    inner.addEventListener('click', () => {
      console.log('2. inner 捕获')
    }, { capture: true })

    inner.addEventListener('click', () => {
      console.log('3. inner 冒泡')
    }, { capture: false })

    // 点击 inner 输出:
    // 1. outer 捕获
    // 2. inner 捕获
    // 3. inner 冒泡
    // 4. outer 冒泡
  </script>
</body>
</html>

关键点

  • 第三个参数可以是布尔值(是否捕获)或配置对象
  • capture: true 在捕获阶段触发,false(默认)在冒泡阶段触发
  • once: true 让事件只触发一次,自动移除监听器
  • passive: true 提升滚动性能,但不能调用 preventDefault()
  • signal 配合 AbortController 可以方便地批量移除监听器