事件模型
# 事件模型
# 事件流的三个阶段
因为 DOM 是树结构,所以当触发子节点时,父子节点之间必然有一个顺序关系。
事件流都会经历三个阶段:
- 事件捕获阶段:在 DOM 树中从上到下传递(从 body 节点到具体节点)
- 处于目标阶段
- 事件冒泡阶段:在 DOM 树中从下到上传递(从具体节点到 body 节点)
# 事件模型的三种分类
# 原始事件模型(DOM0 级)
使用方法:直接在 DOM 的标签上绑定监听函数;或通过 JS 代码绑定
- HTML 代码中直接绑定
<input type="button" onclick="fun()" />
- 通过
JS
代码绑定
var btn = document.getElementById(".btn");
btn.onclick = fun;
2
特点:
- 绑定速度快
- 只支持事件冒泡,不支持事件捕获
- 同一个类型的事件只能绑定一个响应函数(多次绑定会进行替换)
删除方法:btn.onclick=null
# 标准事件模型(DOM2 级)
该事件模型中,一共有三个过程:
- 捕获阶段。从
document
向下传递,依次检查所有元素是否绑定了监听函数,若有则执行 - 事件处理阶段。事件到达目标元素,触发目标元素的响应函数
- 事件冒泡阶段。事件从目标元素冒泡到
document
,依次检查经过的节点是否绑定了事件监听函数,如果有则执行
事件绑定监听函数:
接收三个参数:事件名、响应函数、执行时机。
dom.addEventListener(eventType, handler, useCapture);
useCapture
为true
时,会在捕获阶段执行。反之在冒泡阶段执行。
handler 接收一个 event 参数,可以获取当前元素的标签,以及当前处于的阶段(取值为 1、2、3,对应事件模型的三个阶段):
function onClickFn(event) {
const target = event.target; // 触发事件的元素
const currentTarget = event.currentTarget; // 当前处理元素
const phase = event.eventPhase; // 当前阶段
}
2
3
4
5
事件移除监听函数:
dom.removeEventListener(eventType, handler, useCapture);
# IE 事件模型(基本不用)
IE 事件模型共有两个过程(即没有捕获阶段):
- 事件处理阶段:事件到达目标元素, 触发目标元素的监听函数。
- 事件冒泡阶段:事件从目标元素冒泡到
document
, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行
# 事件委托
# 含义
将元素 A(可能是若干个元素)对于某个事件的响应,委托到元素 B(通常是 A 的父级元素)上执行
委托的事件会在冒泡阶段执行。
特点:
- 提高性能
- 减少重复工作
- 对于没有冒泡机制的事件(如
blur
、focus
)无法委托
# 常见场景
# 1. 多个子元素需要绑定相同的事件
一个列表有 N 个列表项,点击每一项时会输出该项的内容。
若为每个列表项绑定响应函数,则需要绑定 N 次,当 N 很大时会带来性能消耗
<ul id="list">
<li>1</li>
<li>2</li>
<!-- ... -->
<li>N</li>
</ul>
<script>
const list = document.getElementsByTagName("li");
for (let item of list) {
// 需要进行N次绑定
list.onClick = function(el) {
console.log(el.target.innerText);
};
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
将事件委托到父元素:
const ul = document.getElementById("list");
ul.onClick = function(el) {
if (el.target.nodeName === "li") console.log(el.target.innerText);
};
2
3
4
# 2. 用户可以增删元素
例如存在增/删的按钮,每次点击会增/删列表元素,那么在这种情况下就需要频繁的进行事件的绑定和解绑。
将事件委托到父元素则没有这种问题