DOM事件的工作原理

梳理DOM事件的工作原理,是距离真理越来越近的过程

DOM事件的工作原理

导读:
本文是teren对DOM事件知识点所做的进一步整理,整理资料主要参考DOM事件简介饥人谷课件,如果对DOM事件有什么不了解的地方,可以直接参考原文出处。
这篇笔记的目的有两个,一是作为自己整理的资料方便日后查阅,毕竟自己整理的资料用起来更加得心应手,思路更加契合自己;二是希望通过在撰写笔记过程中强化记忆;
如果这篇文章有什么能够帮助到各位读者,我要万分感谢原文的作者以及原文的翻译!!!
最后,这篇文章的整理结构如目录所示。

目录

  1. 1.预备知识 监听事件 移除事件
  2. 事件阶段
  3. event对象
  4. 事件的操作 停止事件传播 阻止浏览器的默认行为 自定义事件 代理事件监听
  5. FQA DOM0事件和DOM2事件 事件流的三种模型 IE兼容性

1. 预备知识

监听事件

在为节点添加事件时,推荐使用addEventListener接口(IE的使用attachEvent接口)

node.addEventListener(eventname,callback[,useCapture])
  • eventname:监听事件的名称,如
    click/mouseover/mouseout/mousedown/mouseup/load/unload等事件
  • callback:事件触发时被调用的函数,此时会自动产生一个event对象,作为第一个参数传入callback
var ul = document.querySelector('ul')
ul.addEventListener('click',function(event){console.log(event.target)})

关于event对象后面详细讲解

  • useCapture:决定回调函数(callback)是否在“捕获(capture)”阶段被触发,,默认是false,即在冒泡阶段被触发
    关于事件阶段后面详解,现在给个范例演示,可以看完事件阶段章节后再“回调”查看(~ ̄▽ ̄)~

demo: usecapture

移除事件

移除事件使用removeEventListener接口

node.removeEventListener(eventname,callback)

值的注意的是:
通过addEventListener添加的事件处理程序只能通过removeEventListener移除,移除时参数与添加的时候相同;
这也就意味着:在移除事件时回调函数不能为匿名函数,因为匿名函数虽然方法体一样,但是句柄(可以理解为函数名)却不相同
也就是说当初定义回调函数必须以以下形成出现

var ul  = document.querySelector('ul')
//method 1
function printHello(){
  alert('hello world')
}
//method 2
handler = function (){
  alert('hello world 2')
}
ul.addEventListener('click',printHello);
ul.addEventListener('click',handler);
ul.addEventListener('click',function(){alert('hello world 3')})
ul.removeEventListener('click',printHello);
//此时你要移除第3个hello world,那么你无法码下去
//ul.removeEventListener('click',)

demo : removeEvent

2.事件阶段

以一个例子去描述事件阶段

  <ul>
    <li id="demo1">demo1</li>
    <li id="demo2">demo2</li>
    <li id="demo3">demo3</li>
  </ul>
var demo2 = document.querySelector('#demo2')
demo2.addEventListener('click',callback)
function callback(){
  console.log('demo')
}

给li#demo2节点添加click事件,在点击li#demo2节点时,点击事件不是直接在该节点直接发生,而是分为三个事件阶段:

  • click事件从html文档的根节点window流向目标节点li#demo2(捕获阶段)
  • 然后在目标节点上click事件触发(目标阶段)
  • 最后再返回到文档的根节点(冒泡阶段)

三个事件阶段
三个事件阶段

事件阶段

demo:event phases
小结:
事件触发的整个过程可分为三个阶段:

  • 捕获阶段
    事件的第一个阶段是捕获阶段。事件从文档的根节点出发,随着DOM树的结构向事件的目标节点流去。途中经过各个层次的DOM节点,并在各节点上触发捕获事件,直到到达事件的目标节点。

类似水流一样,从源头流向目的地

  • 目标阶段
    当事件到达目标节点的,事件就进入了目标阶段。事件在目标节点上被触发,然后会逆向回流,直到传播至最外层的文档节点

【注】
或许有人会疑问?事件在目标节点被触发,那么设置usecapture还有什么用处呢?
我的理解是:
你为节点设置事件是一回事,你触发事件时是另一回事;
节点的事件触发时点可分为上述三个阶段,在乎你怎么设置
下面代码中,li#demo2一定是在目标阶段被触发,而ul则在乎你的设置,下例设置为捕获阶段被触发

var ul = document.querySelector('ul')
var demo2  = document.querySelector('#demo2')
function printUl(){alert('Ul')}
function printList(){
  alert('List')
}
//当点击li#demo2时,到了目标阶段触发li#demo2的click事件
//当你为ul的usecapture设置true时,意味着ul在捕获阶段触发click事件
ul.addEventListener('click',printUl,true)
demo2.addEventListener('click',printList)

demo:how to recognize event phases

  • 冒泡阶段
    事件在目标元素上触发后,并不在这个元素上终止。它会随着DOM树一层层向上冒泡,直到到达最外层的根节点

3.event对象

event对象是在事件第一次触发时候被创建,并且一直伴随着事件在DOM结构中流转的整个生命周期。
event对象会被作为第一个参数传递给事件监听的回调函数。
event对象中包含大量当前事件相关的信息:

属性/方法备注
type事件名称
target事件的目标节点
currentTarget事件触发时的当前节点
bubbles判断节点的事件是否是在冒泡阶段捕获
preventDefault(function)阻止浏览器中用户代理对当前事件的相关默认行为被触发。比如阻止a元素的click事件加载一个新的页面
cancelable(boolean)指明这个事件的默认行为是否可以通过调用event.preventDefault来阻止,即只有cancelable为true的时候,调用event.preventDefault才能生效
stopPropagation(function)阻止当前事件流上后面的元素的回调函数被触发,当前节点上针对此事件的其他回调函数依然会被触发
stopImmediatePropagation(function)阻止当前事件流上后面所有的回调函数被触发,也包括当前节点上针对此事件已绑定的其他回调函数
eventPhase(number)表示当前这个事件所处的阶段(phase):none(0), capture(1),target(2),bubbling(3)
timeStamp(number)事件发生的时间

下面将一些简单的属性放在下面的示例中,复杂的方法将在事件操作章节单独罗列
demo : event simple property

4.事件的操作

停止事件传播

通过调用事件对象的stopPropagation方法,在任何阶段(捕获阶段或者冒泡阶段)中断事件的传播;
此后,事件不会在后面传播过程中的经过的节点上调用任何的监听函数;
demo:stopPropagation
但event.stopPropagation()不会阻止当前节点上此事件其他的监听函数被调用。如果你希望阻止当前节点上的其他回调函数被调用的话,你可以使用更激进的event.stopImmediatePropagation()
方法;
demo:stopImmediatePropagation

阻止浏览器的默认行为

当特定事件发生的时候,浏览器会有一些默认的行为作为反应。例如,使用a元素时会自动添加click事件,当a元素上click事件触发时,它会向上冒泡直到DOM结构的最外层document,浏览器会解释href属性,并且在窗口中加载新地址的内容。
如果我们需要阻止浏览器针对点击事件的默认行为,可以调用event.preventDefault()
demo:preventDefault

自定义事件

【注】
知道有这么一回事,这篇不详讲。

代理事件监听

所谓代理事件监听,指的是不直接在监听的目标节点上添加事件监听函数,而是通过其他的节点代为监听目标节点的事件;

举个例子:
如果有一个列表ul包含了100个子元素li,它们都需要对click事件做出相似的响应,那么我们可能需要查询这100个子元素,并分别为他们添加上事件监听器。这样的话,我们就会产生100个独立的事件监听器

代理事件监听可以让我们更简单的处理这种情况。我们不去监听所有的子元素的click事件,相反,我们监听他们的父元素ul。当一个li元素被点击的时候,这个事件会向上冒泡至ul,触发回调函数。我们可以通过检查事件的event.target属性来判断具体是哪一个li被点击了。
这样一来,仅仅使用了一个上层的事件监听器,并且我们不需要在为添加元素而考虑它的事件监听问题
demo:事件代理
但是在实际代理事件监听中,我们往往使用jQuery提供的on()方法去实现事件代理
demo:事件代理-on()方法

5.FQA

DOM 0级事件处理程序和DOM 2级事件处理程序

首先,了解一下DOM的分级。
DOM是HTML与XML的应用编程接口(API),DOM将整个页面映射为一个由层次节点组成的文件,有1级、2级、3级共3个级别。
1级DOM
1级DOM,由DOM核心与DOM HTML两个模块组成。
DOM核心能映射以XML为基础的文档结构,允许获取和操作文档的任意部分。
DOM HTML通过添加HTML专用的对象与函数对DOM核心进行了扩展。
2级DOM
鉴于1级DOM仅以映射文档结构为目标,DOM 2级面向更为宽广。通过对原有DOM的扩展,2级DOM通过对象接口增加了:
DOM视图:描述跟踪一个文档的各种视图(使用CSS样式设计文档前后)的接口;
DOM事件:描述事件接口;
DOM样式:描述处理基于CSS样式的接口;
DOM遍历与范围:描述遍历和操作文档树的接口;
3级DOM
3级DOM通过引入统一方式载入和保存文档和文档验证方法对DOM进行进一步扩展
"0级"DOM
需要注意的是并没有标准被称为0级DOM,它仅是DOM历史上一个参考点(0级DOM被认为是在Internet Explorer 4.0 与Netscape Navigator4.0支持的最早的DHTML)

也就是说:
DOM 0级事件处理程序是 通过javascript制定事件处理程序的传统方式,具体实现方式是:

var btn = document.getElementById("btn");            
btn.onclick = function(){                
alert(this.id);//this指定当前元素btn  
}
删除DOM0事件处理程序,
只要将对应事件属性置为null即可。btn.onclick = null;

DOM 0级事件处理程序的优点是简单且具有跨浏览器的优势,缺点是一个事件处理程序只能对应一个处理函数

DOM2级事件处理程序是在2级DOM中规定的API,通过addEventListener(IE为attachEvent)去监听事件,具体实现方式是:

var btn = document.getElementById("btn");
function handler(){
  alert(this.id)//this指定当前元素btn
}
btn.addEventListener('click',handler)

demo:addEventListener
同时制定了删除事件处理程序的方法
removeEventListener(IE为detachEvent),关于removeEventListener的注意事项请详见上文移除事件章节;
至于attachEvent与addEventListener的区别详见后文IE兼容性
addEventListener的优点是一个事件处理程序能对应多个处理函数,缺点是存在兼容性问题。

事件流的三种模型

所谓事件流,指的是页面捕获事件的顺序,目前有三种模型:

  • IE的事件冒泡:当发生事件时,目标节点先捕获,然后逐级向上传播到父节点,即事件监听处于冒泡阶段
  • Netscape的事件捕获:当发生事件时,最先触发父节点的事件监听函数,然后逐渐向下传播到目标节点,即事件监听处于捕获阶段
  • 2级DOM规定事件流包括三个阶段,事件捕获阶段,处于目标阶段,事件冒泡阶段

IE兼容性

IE并不支持addEventListener和removeEventListener方法,而是实现了两个类似的方法:
attachEvent(eventname,callback)
detachEvent(eventname,callback)
由于IE指支持事件冒泡,所以添加的程序会被添加到冒泡阶段。
【注意】
IE的事件监听的方法与addEventListener方法不同之处包括:
eventname必须包含on以及没有usecapture;
同时,使用attachEvent方法和addEventListener主要区别在于事件处理程序的作用域。采用addEventListener,事件处理程序会在其所属元素的作用域内运行。使用attachEvent,事件处理程序会在全局作用域内运行,因此this等于window。

var btn = document.getElementById("btn");
function handler(){
  alert(this.id)//this指定window
}
btn.attachEvent('onclick',handler)

添加新评论