什么是 DOM 事件
文档对象模型 (Document Object Model) 是 HTML 和 XML 文档的编程接口。
它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。
文档对象模型(DOM)提供了对同一份文档的另一种表现,存储和操作的方式。 DOM 是 web 页面的完全的面向对象表述,它能够使用如 JavaScript 等脚本语言进行修改。
而事件是 JavaScript 与 HTML 交互的基础。要实现用户与页面的交互,先要通过 DOM 接口访问元素,再对目标元素绑定特定的事件、设置事件处理函数,然后用户触发事件,事件处理函数执行,产生交互效果。
DOM 事件模型和事件流
事件流又称为事件传播,描述的是从页面中接收事件的顺序。当一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
- 事件捕获阶段(capturing phase): 事件从
window
对象自上而下向目标节点传播的阶段; - 处于目标阶段(target phase): 真正的目标节点正在处理事件的阶段;
- 事件冒泡阶段(bubbling phase): 事件从目标节点自下而上向
window
对象传播的阶段。
在DOM2级事件流中,当我们点击下面的代码片段中的 div
元素后,在事件捕获阶段从上到下传播
window –> document -> html -> body –> 目标
而事件冒泡的流程刚好是事件捕获的逆过程,冒泡和捕获的过程如下图:
下面来具体看看事件捕获和事件冒泡。
事件捕获
Netscape 提出的事件流模型称为事件捕获。
事件从最不具体的节点开始接收(document),传递至最具体的节点 ,和 IE 的冒泡相反, 事件捕获的本意是当事件到达预定目标前捕获它。
当我们点击下面的代码片段中的 div
元素时,单击事件会按照如下顺序传播:
document –> html –> body –> div
事件冒泡
IE的事件流称为事件冒泡。
即:事件由最具体的元素接收(div),逐级向上传播到不具体的节点(document)。
当我们点击下面的代码片段中的 div
元素时,单击事件会按照如下顺序传播:
div –> body –> html –> document
几乎现代所有的浏览器都支持事件冒泡,不过有一些细微的差别
- IE5.5 和 IE5.5 - 版本的事件冒泡会跳过 html 元素(body 直接到 document)
- IE9、Firefox、Chrome、Safari则一直冒泡到
window
对象。
事件委托/事件代理
传统的事件处理中,需要为每个元素添加事件处理器。而由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
事件代理的原理用到的就是事件冒泡和目标元素,把事件处理器添加到父元素,等待子元素事件冒泡,并且父元素能够通过 target
(IE为 srcElement
)判断是哪个子元素,从而做相应处理。
事件代理的优点
> 减少内存消耗,提高性能
假设有一个列表,给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。而将多个事件处理器减少到一个,因为事件处理器要驻留内存,这样就提高了性能。
借助事件代理,我们只需要给父容器 ul
绑定方法即可,这样不管点击的是哪一个后代元素,都会根据冒泡传播的传递机制,把容器的绑定的事件触发,然后把对应的方法执行,根据事件源,我们可以知道点击的是谁,从而完成不同的事。
> 动态绑定事件
在很多时候,我们需要通过用户操作动态的增删列表项元素,如果一开始给每个子元素绑定事件,那么在列表发生变化时,就需要重新给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件代理就会省去很多这样麻烦。
事件绑定API
> addEventListener
的第三个参数
网景 和 微软 曾经的战争还是比较火热的,当时, 网景主张 捕获 方式,微软主张 冒泡 方式。后来 W3C 采用折中的方式,平息了战火,制定了统一的标准 —— 先捕获再冒泡。
而 addEventListener
的第三个参数就是为冒泡和捕获准备的。
addEventListener有三个参数:
阻止默认事件/阻止冒泡
阻止默认事件
浏览器的默认事件指的就是浏览器自己的行为。
- 比如我们在点击
<a href="#">
的时候,浏览器跳转到指定页面。 - 当我们滚动鼠标或按空格键和按方向键时页面会向下滚动。
有时候为了更好的用户体验,这时我们就需要阻止浏览器的默认行为。
阻止冒泡
上面了解了捕获和冒泡的流程,那么在有些情况下我们触发事件后不想让事件继续向上冒泡怎么办?
答案是使用 e.stopPropagation()
方法,当触发了这行代码后,事件将停止冒泡。
具体看一个例子 ⬇️ ⬇️ ⬇️
一个 div 里面有一个图标 icon,当用户点击 div 内除了图标的区域时,打印 “点击了一下盒子”,当用户点击 icon 时,打印 “点击了一下icon” 且不打印 “点击了一下盒子”。
资料来源:饥人谷、MDN、DOM事件机制