JavaScript事件机制

510 阅读4分钟

本文主要记录下事件机制之前没有理解到的点(主要是事件流的三个阶段及事件发生顺序)。

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,无所谓捕获和冒泡)