浏览器原理 · 21/51
1. addEventListener 第三个参数 2. addEventListener 与 attachEvent 区别 3. 浏览器兼容性测试与内核 4. 浏览器兼容性问题 5. 浏览器内核与引擎 6. 浏览器图层创建条件 7. 浏览器多进程架构 8. 浏览器渲染机制 9. 浏览器存储方案 10. 浏览器版本检测方法 11. children 与 childNodes 区别 12. 常见浏览器兼容性问题 13. Chrome 页面进程数量 14. 坐标系统对比 15. 多标签页通讯方案 16. 删除 Cookie 17. 自定义事件 18. DOM 事件处理方式演进 19. 元素尺寸属性对比 20. DOM 节点操作 21. DOM 事件机制 22. addEventListener 与 attachEvent 的区别 23. 获取页面所有复选框 24. HTMLCollection 与 NodeList 区别 25. Hybrid 应用开发 26. 强缓存命中机制 27. 浏览器缓存机制 28. 页面编码与资源编码不一致处理 29. jQuery 事件绑定方法对比 30. Input 点击触发的事件顺序 31. JavaScript 浏览器兼容性问题 32. jQuery 多事件绑定实现 33. JSBridge 原理 34. 链接点击后 Hover 失效解决方案 35. 减少重绘和回流的性能优化 36. 移动端 300ms 点击延迟问题 37. 移动端视口配置 38. 移动端点击穿透问题解决 39. 移动端兼容性问题 40. JSBridge 原理与实现 41. 移动端 1px 像素问题解决方案 42. 浏览器渲染流程 43. 页面加载完成事件对比 44. Offset、Scroll、Client 属性对比 45. 同源策略与跨域解决方案 46. Script 标签位置对页面加载的影响 47. Service Worker 与 PWA 48. 存储方案对比:Cookie、Storage、IndexedDB 49. 强缓存默认时间 50. URL 到页面显示的完整过程 51. V8 引擎 JavaScript 执行过程

DOM 事件机制

事件捕获、冒泡、事件委托及 target 与 currentTarget 的区别

问题

解释 DOM 事件机制,包括捕获、冒泡、事件委托,以及 targetcurrentTarget 的区别。

解答

事件流的三个阶段

DOM 事件传播分为三个阶段:

  1. 捕获阶段:从 window 向下传播到目标元素
  2. 目标阶段:到达目标元素
  3. 冒泡阶段:从目标元素向上传播到 window
window

  ▼ 捕获阶段
document


html


body


div (目标元素) ← 目标阶段

  ▼ 冒泡阶段
body


...回到 window

捕获与冒泡

// addEventListener 第三个参数控制监听阶段
// true: 捕获阶段触发
// false(默认): 冒泡阶段触发

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

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

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

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

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

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

target 与 currentTarget

const parent = document.getElementById('parent');
const child = document.getElementById('child');

parent.addEventListener('click', (e) => {
  // target: 实际被点击的元素(事件源)
  console.log('target:', e.target);
  
  // currentTarget: 绑定事件处理器的元素(始终是 parent)
  console.log('currentTarget:', e.currentTarget);
});

// 点击 child 时:
// target: <div id="child">
// currentTarget: <div id="parent">

// 点击 parent 时:
// target: <div id="parent">
// currentTarget: <div id="parent">

事件委托

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

<ul id="list">
  <li data-id="1">Item 1</li>
  <li data-id="2">Item 2</li>
  <li data-id="3">Item 3</li>
</ul>
const list = document.getElementById('list');

// 不推荐:给每个 li 绑定事件
// document.querySelectorAll('li').forEach(li => {
//   li.addEventListener('click', handler);
// });

// 推荐:事件委托
list.addEventListener('click', (e) => {
  // 判断点击的是否是 li 元素
  if (e.target.tagName === 'LI') {
    const id = e.target.dataset.id;
    console.log('点击了:', id);
  }
});

// 更健壮的写法:处理点击 li 内部元素的情况
list.addEventListener('click', (e) => {
  const li = e.target.closest('li');
  if (li && list.contains(li)) {
    console.log('点击了:', li.dataset.id);
  }
});

阻止传播与默认行为

element.addEventListener('click', (e) => {
  // 阻止事件继续传播(捕获或冒泡)
  e.stopPropagation();
  
  // 阻止同一元素上的其他监听器执行
  e.stopImmediatePropagation();
  
  // 阻止默认行为(如链接跳转、表单提交)
  e.preventDefault();
});

完整示例

<!DOCTYPE html>
<html>
<body>
  <div id="grandparent" style="padding: 50px; background: #f0f0f0;">
    Grandparent
    <div id="parent" style="padding: 50px; background: #ddd;">
      Parent
      <div id="child" style="padding: 50px; background: #bbb;">
        Child
      </div>
    </div>
  </div>

  <script>
    const elements = ['grandparent', 'parent', 'child'];
    
    elements.forEach(id => {
      const el = document.getElementById(id);
      
      // 捕获阶段
      el.addEventListener('click', (e) => {
        console.log(`${id} 捕获, target: ${e.target.id}`);
      }, true);
      
      // 冒泡阶段
      el.addEventListener('click', (e) => {
        console.log(`${id} 冒泡, target: ${e.target.id}`);
      });
    });
    
    // 点击 child 输出:
    // grandparent 捕获, target: child
    // parent 捕获, target: child
    // child 捕获, target: child
    // child 冒泡, target: child
    // parent 冒泡, target: child
    // grandparent 冒泡, target: child
  </script>
</body>
</html>

关键点

  • 事件流:捕获阶段 → 目标阶段 → 冒泡阶段
  • addEventListener 第三个参数为 true 时在捕获阶段触发
  • target 是触发事件的元素,currentTarget 是绑定监听器的元素
  • 事件委托利用冒泡,在父元素上处理子元素事件,减少内存占用,支持动态元素
  • stopPropagation() 阻止传播,preventDefault() 阻止默认行为