浏览器的事件模型是什么?事件委托的原理?

事件流三个阶段

1. 捕获阶段(Capture):从 window → 目标元素的祖先
2. 目标阶段(Target):到达目标元素
3. 冒泡阶段(Bubble):从目标元素 → window
// 第三个参数:true = 捕获阶段监听,false/默认 = 冒泡阶段监听
element.addEventListener('click', handler, false) // 冒泡
element.addEventListener('click', handler, true)  // 捕获

// stopPropagation:阻止事件传播
element.addEventListener('click', (e) => {
  e.stopPropagation() // 阻止冒泡
})

// stopImmediatePropagation:阻止同元素上其他监听器执行
element.addEventListener('click', (e) => {
  e.stopImmediatePropagation()
})

事件委托

利用事件冒泡,在父元素上统一处理子元素的事件。

// ❌ 为每个 li 绑定事件
document.querySelectorAll('li').forEach(li => {
  li.addEventListener('click', handleClick)
})

// ✅ 事件委托:在父元素上绑定一次
document.querySelector('ul').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    console.log(e.target.textContent)
  }
})

事件委托优势

  1. 减少事件监听器数量,节省内存
  2. 动态元素自动处理:后添加的 li 也能响应点击
  3. 方便管理:统一在一个地方处理

事件委托 + data 属性

<div id="toolbar">
  <button data-action="save">保存</button>
  <button data-action="delete">删除</button>
  <button data-action="cancel">取消</button>
</div>
document.getElementById('toolbar').addEventListener('click', (e) => {
  const action = e.target.closest('[data-action]')?.dataset.action
  if (!action) return
  
  switch (action) {
    case 'save': saveData(); break
    case 'delete': deleteData(); break
    case 'cancel': cancelAction(); break
  }
})

closest 方法

closest() 从当前元素向上查找匹配的祖先(包括自身),非常适合事件委托:

// 点击按钮内的 span 时也能找到 button
button.addEventListener('click', (e) => {
  const btn = e.target.closest('button')
  if (btn) handleClick(btn)
})

不适合事件委托的事件

事件循环与事件

点击事件在宏任务中执行。如果事件处理函数中有异步操作,注意事件可能在异步操作完成前被再次触发。

同分类其他题目