DOM 事件模型
事件模型描述的是从页面中接收事件的顺序。事件发生时会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即 DOM 事件模型。
DOM 事件模型分为三个阶段:
- 事件捕获阶段
- 事件目标阶段
- 事件冒泡阶段
事件捕获阶段
捕获阶段是从外到内传播即从根节点向最内侧节点传播。
事件捕获(event capturing)是由 Netscape 团队提出的一种事件模型,它认为不太具体的节点应该更早接受事件,而最具体的节点应该最后接受到事件,另一种说法就是由外向内找监听函数。
以上图为例,点击 td 元素后触发 click 事件的顺序是:
- Window
- Document
- html
- body
- table
- tbody
- tr
- td
事件目标阶段
在目标节点触发事件时,就处于事件目标阶段。
事件冒泡阶段
冒泡阶段就是从内向外传播直到根节点结束。
事件冒泡(event bubbling)是由 IE 团队提出的事件模型,它认为事件开始时由最具体的元素接受,然后逐级向上传播到不具体的节点,或者说由内向外找监听函数。
仍以上图为例,点击 td 元素后触发 click 事件的顺序是:
- td
- tr
- tbody
- table
- body
- html
- Document
- Window
DOM 事件模型的顺序
根据上述内容可知,Netscape 和 IE 各自提出的事件模型完全相反,所以为了统一,W3C 在 2002 年发布了标准(DOM Level 2 Events Specification),其中规定浏览器应同时支持两种调用顺序:在三个阶段中,首先经过事件捕获阶段,然后是实际目标接收到事件即处于目标阶段,最后再经过事件冒泡阶段,如上图所示。
当使用事件监听器(addEventListener)时,addEventListener 接收三个参数:第一个是绑定的事件类型;第二个是当事件被触发时要执行的函数;第三个是一个 Boolean 类型的可选参数,它决定了浏览器应该在哪个阶段(冒泡/捕获)发现元素有监听函数时去调用监听函数:
- false 代表在冒泡阶段,默认为false
- true 代码在捕获阶段
<div id="mapoTofu"></div>
<script>
var mapoTofu = document.querySelector("#mapoTofu")
mapoTofu.addEventListener('click',function(){
console.log('在捕获阶段调用函数')
}, true)
</script>
事件委托
事件委托(事件代理)是指将原本要绑定在目标元素的响应事件委托给父元素,让父元素来监听事件,其原理是 DOM 事件模型中的事件冒泡,当事件响应到目标元素上时,会通过事件冒泡机制从而触发外层父元素的绑定事件。
应用场景
例如有一个列表,其中有多个列表项,同时有一个按钮,点击按钮后会新增一个列表项,代码如下:
- HTML
<ul id="mapoTofu">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<button>点击添加一个 li 元素</button>
如果给每个 li 元素都绑定⼀个监听函数,那对于内存消耗是⾮常⼤的,同时无法对将来出现但当前不存在的 li 元素绑定监听函数,所以需要使用事件委托:
const list = document.querySelector("#mapoTofu")
list.onclick = function (event) {
event = event || window.event
const target = event.target
if (target.nodeName.toLocaleLowerCase === 'li') {
alert(target.innerHTML)
}
}
优点
综上所述,事件委托有两个优点:
- 节省内存
- 可以监听动态元素