章节索引 :

DOM 事件流

DOM 事件流描述了 DOM 时间响应的阶段、路径。

DOM 事件流也会被称为 DOM 事件模型。

图片描述

1. 事件流阶段

事件流有三个阶段:

  1. 捕获阶段 从window开始,寻找触发事件最深层的节点,过程中如果有节点绑定了对应事件,则触发事件
  2. 目标阶段 找到事件触及的最深节点
  3. 冒泡阶段 从最深节点按照捕获的路径进行返回,过程中如果有节点绑定了对应事件,则触发事件

现代浏览器默认都会在冒泡阶段触发事件。

通过一个例子来简单的感受一下。

实例演示
预览 复制
复制成功!
<style>
  .box {
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .size-100 {
    width: 100px;
    height: 100px;
    background: #4caf50;
  }

  .size-200 {
    width: 200px;
    height: 200px;
    background: chocolate;
  }

  .size-300 {
    width: 300px;
    height: 300px;
    background: wheat;
  }
</style>

<div class="box size-300">
  <div class="box size-200">
    <div class="box size-100">
    </div>
  </div>
</div>

<div class="result"></div>

<script>
  var boxes = document.querySelectorAll('.box');
  var result = document.querySelector('.result');

  boxes.forEach(function(box) {
    box.addEventListener('click', function() {
      var el = document.createElement('p');

      el.innerText = '现在触发点击事件的是' + this.className;

      result.appendChild(el);
    });
  });
</script>
运行案例 点击 "运行案例" 可查看在线运行效果

图片描述

点击后,观察输出可以发现,事件是点击到的最深层次的节点开始向上执行的。

即从 size-100size-200size-300,这就是冒泡的过程。

如果想让事件在捕获阶段就执行,可以传递 addEventListener 方法第三个参数。

2. addEventListener 的第三个参数

addEventListener 的第三个参数用来决定事件在冒泡阶段触发还是在捕获阶段触发,其为一个布尔值,传递 false 则事件会在冒泡阶段触发,传递 true 则会在捕获阶段触发。

实例演示
预览 复制
复制成功!
<style>
  .box {
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .ele1 {
    background: wheat;
    width: 200px;
    height: 200px;
  }

  .ele2 {
    background: yellowgreen;
    width: 100px;
    height: 100px;
  }
</style>

<div class="box ele1">
  <div class="box ele2"></div>
</div>

<div class="result"></div>

<script>
var ele1 = document.querySelector('.ele1');
var ele2 = document.querySelector('.ele2');
var result = document.querySelector('.result');

function getElement(content) {
  var el = document.createElement('p');

  el.innerText = content;

  return el;
}

ele1.addEventListener('click', function() {
  result.appendChild(getElement('我是元素ele1'));
});

ele2.addEventListener('click', function() {
  result.appendChild(getElement('我是元素ele2'));
});
</script>
运行案例 点击 "运行案例" 可查看在线运行效果

图片描述

根据默认浏览器事件是在冒泡阶段触发的规则,上述例子会先触发子节点 .ele2 的事件,再触发 .ele1 的事件。

如果想让 .ele1 在捕获阶段就触发事件,则在绑定事件的时候传递第三个参数为 true 即可。

实例演示
预览 复制
复制成功!
<style>
  .box {
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .ele1 {
    background: wheat;
    width: 200px;
    height: 200px;
  }

  .ele2 {
    background: yellowgreen;
    width: 100px;
    height: 100px;
  }
</style>

<div class="box ele1">
  <div class="box ele2"></div>
</div>

<div class="result"></div>

<script>
var ele1 = document.querySelector('.ele1');
var ele2 = document.querySelector('.ele2');
var result = document.querySelector('.result');

function getElement(content) {
  var el = document.createElement('p');

  el.innerText = content;

  return el;
}

ele1.addEventListener('click', function() {
  result.appendChild(getElement('我是元素ele1'));
}, true);

ele2.addEventListener('click', function() {
  result.appendChild(getElement('我是元素ele2'));
});
</script>
运行案例 点击 "运行案例" 可查看在线运行效果

图片描述

这样 .ele1 的事件就会在捕获阶段触发。

3. 不符合W3C标准的事件流

早期的 IE 和 Netscape Navigator 是不符合标准的。

前者是使用事件冒泡流,后者使用事件捕获流。

前面的章节有提到过 0级DOM事件 ,其提供的绑定事件的方式是不能指定事件触发的阶段的,其原因是在那个阶段下,还没有现在制定的 DOM 事件流

当时并没有统一的标准,0级DOM事件也并不是一套官方出台的标准,所有相关内功全部由浏览器厂商决定。

后来 W3C 很好的整合了这两种模型,便有了现在的 DOM 事件流。

4. 冒泡的终点元素

这个问题其实经常会在面试中被问到,通常题目会是这样的:

请描述一下事件捕获和冒泡的具体流程

其实问的是事件从那个节点开始捕获,然后到目标节点,最后又在哪个节点冒泡结束。

大部分面试者会回答 document,其实根据事件对象的 path 属性就可以得到答案。

图片描述

path 属性会返回事件冒泡的路径,其最后是到 window 对象才停止的。

其实这点在标准中也有描述。

注意:path 属性有兼容性问题,可以通过 can i use 确定。可以用标准中的 composedPath 代替。

5. 小结

开发过程中很少会取改变事件触发的阶段。但是事件流的概念依然重要,因为很多时候要阻止事件冒泡。

理解了事件流,可以理解事件委托的原理,事件委托相关的内容可以参阅事件相关的性能优化。

前置知识
什么是JavaScript 开发与学习环境准备 调试方案
基础
JavaScript 变量 JavaScript 数据类型 JavaScript if 语句 JavaScript for 语句 JavaScript 算数运算符 JavaScript 比较运算符 JavaScript 逻辑运算符 JavaScript 表达式 JavaScript 函数 JavaScript 对象 JavaScript 字符串 JavaScript 数字 JavaScript 数组 JavaScript switch 语句 JavaScript while 语句 JavaScript break与continue JavaScript with document.cookie
内置对象
JavaScript Function JavaScript Math JavaScript Date JavaScript RegExp JavaScript JSON
JavaScript 与 DOM
什么是DOM DOM和JavaScript的关系 获取和操作 DOM 节点 JavaScript DOM与事件 JavaScript DOM 事件绑定 JavaScript DOM 事件对象 JavaScript DOM 事件流 JavaScript DOM 事件优化 JavaScript DOM 自定义事件
表单处理
使用 JavaScript 校验表单
BOM
BOM window 对象 常用的 BOM 相关对象 BOM 常用属性和方法
AJAX
JavaScript AJAX
进阶知识
JavaScript 异常处理 JavaScript 三元运算符 JavaScript 逗号操作符 JavaScript void JavaScript typeof JavaScript delete JavaScript debugger JavaScript getter &setter JavaScript 原型 JavaScript new操作符和构造函数 JavaScript instanceof JavaScript this JavaScript 严格模式 JavaScript 作用域 JavaScript 闭包 JavaScript 变量提升 JavaScript 对象包装器
常用库
jQuery Lodash moment.js swiper
进阶指南
ECMAScript6 Node.js Babel CSS 预处理器 代码规范 TypeScript Web Components 小程序 Vue / React / Angular JavaScript 关键字
常见疑点与误区
分号问题 对象属性访问问题 this 使用问题 浮点数精度问题 独一无二的 NaN 避免全局污染 控制台观察对象问题 根据环境选择语言特性
扩展
相关资源