事件流

最近在复习,回过头看了一下事件流,将自己的理解和总结写一下.
补充了 event.currentTarget 和 event.target 的区别.

何为事件流

事件流是从页面中接受事件的顺序。

当你点击了页面的一个按钮的同时,你也点击了包括此按钮的容器元素,甚至也可以认为点击了整个页面。
为了解决这种情况,事件流的概念就诞生了。

事件流分为两种,由两个浏览器开发团队提出,分别是事件冒泡流事件捕获流
现在的浏览器基本上都实现了这两种事件流模型。

事件冒泡

顾名思义,就是从下往上传播。

在事件开始时,由最具体的元素接收事件,就是嵌套层次最深的那个节点,然后依次往上级元素传播,直到到达顶层对象(现在的浏览器实现都是冒泡到window对象)

事件捕获

与冒泡模型相反,捕获模型是从上往下传播。

在事件开始,由顶层对象接收事件,然后沿着 DOM 树依次向下传播,直到到达事件的实际目标。(浏览器一般也是从 window 对象开始捕获事件的,)

1
2
3
4
5
6
7
8
<html>
<body>
<div>
<button type="button" onclick="alert('button')">
</button>
</div>
</body>
</html>

当你点击 button,采用冒泡模型,事件会以如下的顺序传播

  1. <button> -> <div> -> <body> -> <html> -> Document对象 -> window对象

采用捕获模型,事件会以如下的顺序传播

  1. window对象 -> Document对象 -> <html> -> <body> -> <div> -> <button>

DOM 事件流

DOM2级事件规定事件流包括三个阶段: 事件捕获阶段处于目标阶段事件冒泡阶段

捕获阶段只从window对象到达<div>就停止了,下一个阶段是目标阶段,事件在<button>上发生,(事件处理会被看成冒泡阶段的一部分),然后冒泡阶段开始,事件由下往上传播。

事件处理程序

一个事件,可以是click,也可以是mouseoverDOMContentLoadedload,详情参考MDN

响应一个事件的函数,叫做事件处理程序,也可以叫事件监听器,当事件发生时,对应的事件处理程序就会执行。

有三种方式可以注册事件监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<body>
<!-- Method 1 -->
<div id="test" onclick="console.log("By Html Attribute")">
</div>
</body>
<script>
(function register() {
var test = document.getElementById('test');

//Method 2
test.onload = function(event) {
console.log(" By DOM Element Attribute");
}
//Method 3
test.addEventListener("mouseover", function(event) {
console.log("By addEventListener");
})
})()
</script>
</html>

事件委托

当页面中的事件处理程序过多的时候,会占用过多内存空间,解决办法就是事件委托

事件委托利用了事件冒泡,只需要指定一个事件处理程序,就可以管理某一类型的事件。

实践

当你想给每一个 li 都添加一个事件处理程序时,你需要添加三个事件处理程序,当你添加的事件处理程序过多时,页面的性能会受到影响。

使用事件委托,只需在 DOM 树中尽量最高的层次上添加一个事件处理程序,可以有效地减少事件处理程序的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
;<ul id="List">
<li id="item1">1</li>
<li id="item2">2</li>
<li id="item3">3</li>
</ul>

var list = document.getElementById('List')

list.addEventListener(
'click',
function(event) {
var target = event.target || event.srcElement //兼容写法
//事件对象的target属性为一个Element对象,保存着当前点击的目标元素的信息
console.log(target)
},
false
)

event 对象的 currentTarget 和 target 的区别

当多个有明显父子关系的元素都绑定了同一事件时,currentTarget 指向的是当前事件绑定的元素,event.target 指的是触发了该绑定事件的目标元素。
cunrrentTarget 发生在事件流的整个阶段(捕获,目标阶段,冒泡),event.target 只是指在目标阶段发生的元素。

通过下面的例子来理解

addEventListener 的第三个参数 useCapture 的默认值为 false,也就是说在冒泡阶段接收到该事件。

当点击 inner 元素时,事件从 inner 元素从下往上冒泡传播,所以 currentTarget 一开始指向的是 inner,随着事件冒泡传播,currentTarget 指向了其他绑定了 Click 事件的元素。只有在目标阶段的时候,event.target 才会等于 event.currentTarget

1
2
3
4
5
6
7
8
9
10
currentTarget: inner
target: inner
currentTarget: fourth
target: inner
currentTarget: third
target: inner
currentTarget: second
target: inner
currentTarget: first
target: inner

如果把所有监听函数在注册时useCapture参数的值置为 true,则说明在捕获阶段接收事件,则 inner 的父级元素会先于 inner 接收到 click 事件,所以结果打印顺序会颠倒,变为下面所示。

1
2
3
4
5
6
7
8
9
10
currentTarget: first
target: inner
currentTarget: second
target: inner
currentTarget: third
target: inner
currentTarget: fourth
target: inner
currentTarget: inner
target: inner