- 事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。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)。
这种三阶段的传播模型,使得同一个事件会在多个节点上触发。(图示如下)
DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。
- 我们可以使用
baba.addEventListener('click', fn, bool)来监听事件- 如果 bool 不传或为 false;就让 fn 走冒泡,即当浏览器在冒泡阶段发现 baba 有 fn 监听函数,就会调用 fn,并提供事件信息
- 如果 bool 为 true;就让 fn 走捕获,即当浏览器在捕获阶段发现 baba 有 fn 监听函数,就会调用 fn ,并提供事件信息
- 注意:不管传与不传,整个过程(先从外到内,再从内到外)都是要走的,关键是确定了fn在哪被调用
- 捕获事件就从外到内
- 冒泡事件就从内到外
代码示例: 事件捕获和事件冒泡
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,看到Bubbles和Cancelable Bubbles:该事件是否冒泡(所有冒泡都可取消)Cancelable:开发者是否可以阻止默认事件Cancelable与冒泡无关
- MDN 搜索
注意:有时候在冒泡阶段的事件没法取消,并非不可取消冒泡,是事件不能阻止默认动作。
-
如何阻止滚动
scroll事件不可阻止默认动作- 阻止 scroll 默认动作没用,因先有滚动才有滚动事件。要阻止滚动,可阻止
wheel和touchstart的默认动作 - 注意你需要找准滚动条所在的元素。但是滚动条还能用,可用 CSS 让滚动条 width: 0
- CSS 也行
- 使用 overflow: hidden 可以直接取消滚动条。但此时 JS 依然可以修改 scrollTop
(注意:滚动条存在与否是不影响事件滚动的)
代码示例: 阻止滚动
事件委托
委托一个元素监听本该自己监听的东西。一般我们可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件。
自定义事件
事件委托应用实例
- 场景一
- 你要给 100 个按钮添加点击事件,咋办?
答:监听这 100 个按钮的祖先,等冒泡的时候判断 target 是不是这 100 个按钮中的一个
代码示例:事件一
- 场景二
- 你要监听目前不存在的元素的点击事件,咋办?
答:监听祖先,等点击的时候看看是不是我想要监听的元素即可
代码示例: 事件二
事件委托优点
- 省监听数(内存)
- 可以监听动态元素
封装事件委托
要求:
- 写出这样一个函数 on('click', '#div1', 'button', fn)
- 当用户点击 #div1 里的 button 元素时,调用 fn 函数
- 要求用到事件委托
这里有个问题:上述代码示例的父元素就一个,如果被点击的元素不止一个父元素怎么办?
资料来源: