DOM 事件机制

98 阅读4分钟

事件

事件是在编程时系统产生的动作或者发生的事情,系统响应事件后,可以通过某种方式对事件作出回应。 举例:如果用户在网页上点击一个按钮,开发者可能想通过显示一个对话框或动画来响应这个动作。

每个可用的事件都有一个事件处理器,事件触发时会运行这部分代码块。 当我们定义了一个用来回应被触发事件的代码块的时候,可以说我们注册了一个事件处理器。事件处理器有时候也被叫做事件监听器,严格地说,它们共同工作。监听器监听事件是否发生,然后处理器就是对事件做出回应。

事件流

<div class=爷爷>
    <div class=爸爸>
        <div class=儿子>
            文字
        </div>
    </div>
</div>
<--!给爷爷,爸爸,儿子三个div分别添加事件监听-->

DOM是个树形结构,对于这部分代码,当我们在页面上单击 文字 的时候,其父元素或者祖先元素事件是否会触发? 以及在事件的触发顺序上,是由子-父-爷的顺序触发,还是相反?这些就关联到了事件流。

事件流是一个事件沿着特定数据结构传播的过程。其中,冒泡和捕获是事件流在DOM中两种不同的传播方法。

事件流有三个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段
事件冒泡

事件冒泡(event bubbling)是IE的事件流,如同像水中投掷石头,泡泡从水底浮到水面。事件会从某个具体的元素接收,然后逐级向上传播,在每一级节点上都会发生,最后传播到document对象。

以之前的代码为例,在文字元素上发生click 事件的顺序是 div#son -> div#father -> div#grandfather -> body -> html ->document

事件捕获

事件捕获(event capturing)是网景提出的事件流。是从最外层对象开始发生,指导最具体的元素。

以之前的代码为例,在文字元素上发生click 事件的顺序是 document -> html -> body ->grandfather -> father ->son

示意图

image.png

addEventListener()的第三个参数

当时的浏览器大战,网景和微软打得火热,后来和事佬W3C指定了统一的标准,先捕获再冒泡

addEventListener()的第三个参数就是为此准备的

element.addEventListener(event, fn, bool)

第一个参数event是需要绑定的事件;

第二个参数是触发事件后要被执行的函数;

第三个参数是一个布尔值,默认为false; 如果bool不传或为falsy:就让fn走冒泡路径,即当浏览器在冒泡阶段发现element有fn监听函数,就会调用n, 如果bool为true:就让fn走捕获路径,即当浏览器在捕获阶段发现element有fn监听函数,就会调用fn,并提供事件信息。

当事件捕获和事件冒泡一起存在时,事件如何触发? (记被点击的DOM节点为target节点)

  1. document 往 target节点,捕获前进,遇到注册的捕获事件立即触发执行;
  2. 到达target节点,触发事件(对于target节点上,是先捕获还是先冒泡则捕获事件和冒泡事件的注册顺序,先注册先执行);
  3. target节点 往 document 方向,冒泡前进,遇到注册的冒泡事件立即触发。

事件对象

在事件处理函数内部,可能会看到一个固定指定名称的参数,例如eventevt或简单的e。 这被称为事件对象,它被自动传递给事件处理函数,以提供额外的功能和信息。

event对象的操作

  1. event. preventDefault()阻止默认事件

调用此方法将不触发默认事件,如表单一点击提交按钮(submit)跳转页面、a标签默认页面跳转或是锚点定位等。

  1. event.stopPropagation()阻止事件冒泡

事件冒泡阶段是指事件从目标节点自下而上向window对象传播,当事件使用event.stopPropagation()将阻止事件冒泡到父元素,阻止父事件监听程序被执行。

事件委托

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。

事件委托可以减少内存消耗,提高性能。

动态绑定事件

在很多时候,我们需要通过用户操作动态的增删子元素,如果一开始给每个子元素绑定事件,那么在列表发生变化时,就需要重新给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件委托就会省去很多这样麻烦。