本文主要记录下事件机制之前没有理解到的点(主要是事件流的三个阶段及事件发生顺序)。
DOM事件级别
DOM级别分为四个级别:DOM0级、DOM1级、DOM2级、DOM3级;
DOM事件分为三个级别:DOM0级事件处理,DOM2级事件处理和DOM3级事件处理。由于DOM1级中没有事件的相关内容,所以没有DOM1级事件。

DOM0级事件
// 行内事件(标签中写事件)
<div id="btn" onclick="alert('hello world!')">按钮</div>
// el.onclick = function(){}
<button id="btn" type="button"></button>
<script>
var btn = document.getElementById('btn')
btn.onclick = function() {
console.log('Hello World');
}
</script>
注意:DOM0级事件无法为同一个元素/标签绑定多个同类型事件
DOM2级事件
//el.addEventListener(event-name, callback, useCapture)
<button id="btn" type="button"></button>
<script>
var btn = document.getElementById('btn');
btn.addEventListener('click', showFn, false)
btn.addEventListener('click', showFn2, false)
function showFn() {
alert('Hello World');
}
function showFn2() {
alert('Hello World2');
}
</script>
可以为事件设置多个事件处理函数,可以通过第三个参数(useCapture)设置在什么阶段执行事件处理函数,默认是false,即在事件冒泡阶段执行事件处理函数。
DOM3级事件
在DOM 2级事件的基础上添加了更多的事件类型。
UI事件,当用户与页面上的元素交互时触发,如:load、scroll
焦点事件,当元素获得或失去焦点时触发,如:blur、focus
鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup
滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
文本事件,当在文档中输入文本时触发,如:textInput
键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified
同时DOM3级事件也允许使用者自定义一些事件。
// 自定义事件
var event = new Event('build');
// Listen for the event
elem.addEventListener('build', function (e) { ... }, false);
// Dispatch the event
elem.dispatchEvent(event);
DOM事件流
DOM2级事件规定事件流包括三个阶段(我们可通过事件对象的eventPhase属性,得知事件处于哪个阶段):
(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
(2)目标阶段:真正的目标节点正在处理事件的阶段;
(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
发生的顺序是:事件捕获阶段 --> 目标事件阶段 --> 事件冒泡阶段

target和currentTarget
事件对象中有两个容易混淆的属性:target和currentTarget。
target是触发事件的某个具体的对象,只会出现在事件机制的目标阶段,即“谁触发了事件,谁就是 target”;
currentTarget是绑定事件的对象;
捕获型和冒泡型事件同时存在,谁生效?
<div id="parent">
<div id="child"></div>
</div>
<script>
var parent = document.getElementById('parent');
var child = document.getElementById('child');
parent.addEventListener("click",function(e){
console.log("parent 冒泡事件");
},false);
child.addEventListener("click",function(e){
console.log("child 冒泡事件");
},false);
parent.addEventListener("click",function(e){
console.log("parent 捕获事件");
},true);
child.addEventListener("click",function(e){
console.log("child 捕获事件");
},true);
</script>
// 点击id为child的内层元素,打印顺序为:
// parent 捕获事件
// child 冒泡事件
// child 捕获事件
// parent 冒泡事件
点击child,click事件从document->html->body->parent->child(捕获前进)这里在parent上发现了捕获注册事件,则输出"parent 捕获事件",到达目标节点child,已经到达目标节点。
child上注册了冒泡和捕获事件,先注册的冒泡后注册的捕获,则先执行冒泡,输出"child 冒泡事件",再在child上执行后注册的事件,即捕获事件,输出"child 捕获事件"。
最后进入冒泡阶段,按照child-parent->body->html->documen(冒泡前进) 在parent上发现了冒泡事件,则输出"parent 冒泡事件"。
点击parent,click事件从document->html->body->parent(捕获前进),已经到达目标节点。
parent上注册了冒泡和捕获事件,先注册的冒泡后注册的捕获,则先执行冒泡,输出"parent 冒泡事件",再在parent上执行后注册的时间,即捕获事件,输出"parent 捕获事件"。
借用上面这个例子,强调下事件流三个阶段中的目标阶段。当事件流到达target节点,触发事件(先注册先执行)。
简单总结一下:
(1)当元素发生事件时,是要先判断是否处于事件阶段二(即e.taregt === e.currentTarget),有没有到达目标节点。
(2)若e.taregt !== e.currentTarget(即非目标阶段)先捕获后冒泡
(3)在target注册的事件监听器,不分捕获和冒泡,先注册先执行(因为当事件传递到target时,e.eventPhase均为2,即AT_TARGET,此时e.taregt === e.currentTarget,无所谓捕获和冒泡)