DOM事件与事件委托

106 阅读5分钟
  • 事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。DOM 支持大量的事件
  • 浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。

通过一个引例来开始认识:

引例(点击事件):

如果我们要对下面一段 HTML 代码添加事件监听,即对三个 div (爷爷>.爸爸>.儿子)分别添加事件监听 fnYe / fnBa / fnEr

<div class=爷爷>
  <div class=爸爸>
    <div class=儿子>
    文字
    </div>
  </div>
</div>

问题 1 :

  • 点击文字,算不算点击儿子?
  • 点击文字,算不算点击爸爸?
  • 点击文字,算不算点击爷爷?

(答案:都算)


问题 2 :

  • 点击文字,最先调用 fnYe / fnBa / fnEr 中的哪一个函数?

(答案:都行)

在过去 IE5 认为先调 fnEr,网景认为先调 fnYe。之后在2002 年,W3C 发布文档名为 【DOM Level 2 Events Specification (DOM 级别 2 事件规范)】的标准。规定浏览器应该同时支持两种调用顺序。首先按 “爷爷=>爸爸=>儿子” 顺序看有没有函数监听,然后按 “儿子=>爸爸=>爷爷” 顺序看有没有函数监听。有监听函数就调用,并提供事件信息,没有就跳过。

在这个过程当中,DOM事件机制分为2个阶段:

  • 从外向内找监听函数,叫事件捕获
  • 从内向外找监听函数,叫事件冒泡

追问:

  • fnYe / fnBa / fnEr 都调用两次?

(非也!开发者自己选择把 fnYe 放在捕获阶段还是放在冒泡阶段)


事件捕获与事件冒泡

一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

  • 第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。
  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
  • 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。

这种三阶段的传播模型,使得同一个事件会在多个节点上触发。(图示如下)

image.png

DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。

  • 我们可以使用 baba.addEventListener('click', fn, bool) 来监听事件
    • 如果 bool 不传或为 false;就让 fn 走冒泡,即当浏览器在冒泡阶段发现 baba 有 fn 监听函数,就会调用 fn,并提供事件信息
    • 如果 bool 为 true;就让 fn 走捕获,即当浏览器在捕获阶段发现 baba 有 fn 监听函数,就会调用 fn ,并提供事件信息
    • 注意:不管传与不传,整个过程(先从外到内,再从内到外)都是要走的,关键是确定了fn在哪被调用
      • 捕获事件就从外到内
      • 冒泡事件就从内到外

image.png

代码示例: 事件捕获和事件冒泡

target 和 currentTarget的区别

  • e.target 用户正在操作的元素
  • e.currentTarget 程序员在监听的元素
  • 操作和监听的可能是同一个元素,也可能是不同的元素

例如:

<div>
  <span>文字</span>
</div>

假设我们监听的是div, 但用户实际点击的是文字,那么

  • e.target就是span标签
  • e.currentTarget就是div标签

取消冒泡

  • 捕获不可取消,但冒泡可以
  • e.stopPropagation() 可中断冒泡,浏览器不再向上走;一般用于封装某些独立的组件

代码示例: 取消冒泡

事件特性

  • 阻止默认动作:e.preventDefault()MDN
  • 有些事件不能阻止默认动作,例如:scroll(滚动)
    • MDN 搜索 scroll event,看到 BubblesCancelable
    • Bubbles :该事件是否冒泡(所有冒泡都可取消)
    • Cancelable :开发者是否可以阻止默认事件
    • Cancelable 与冒泡无关

image.png

注意:有时候在冒泡阶段的事件没法取消,并非不可取消冒泡,是事件不能阻止默认动作。

  • 如何阻止滚动

    • scroll 事件不可阻止默认动作
    • 阻止 scroll 默认动作没用,因先有滚动才有滚动事件。要阻止滚动,可阻止 wheeltouchstart 的默认动作
    • 注意你需要找准滚动条所在的元素。但是滚动条还能用,可用 CSS 让滚动条 width: 0
    • CSS 也行
      • 使用 overflow: hidden 可以直接取消滚动条。但此时 JS 依然可以修改 scrollTop

(注意:滚动条存在与否是不影响事件滚动的)

代码示例: 阻止滚动

事件委托

委托一个元素监听本该自己监听的东西。一般我们可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件。

自定义事件

  • 浏览器自带事件,一共100多种事件,事件参考 在 MDN 上
  • 开发者能在自带事件之外,自定义一个事件,例如: 点击触发事件

事件委托应用实例

  • 场景一
    • 你要给 100 个按钮添加点击事件,咋办?

答:监听这 100 个按钮的祖先,等冒泡的时候判断 target 是不是这 100 个按钮中的一个

代码示例:事件一

  • 场景二
    • 你要监听目前不存在的元素的点击事件,咋办?

答:监听祖先,等点击的时候看看是不是我想要监听的元素即可

代码示例: 事件二

事件委托优点

  • 省监听数(内存)
  • 可以监听动态元素

封装事件委托

要求:

  • 写出这样一个函数 on('click', '#div1', 'button', fn)
  • 当用户点击 #div1 里的 button 元素时,调用 fn 函数
  • 要求用到事件委托

事件封装代码示例

这里有个问题:上述代码示例的父元素就一个,如果被点击的元素不止一个父元素怎么办?

代码示例


资料来源: