DOM 事件流
DOM 事件流描述了 DOM 時間響應的階段、路徑。
DOM 事件流也會被稱為 DOM 事件模型。
1. 事件流階段
事件流有三個階段:
捕獲階段
從window開始,尋找觸發事件最深層的節點,過程中如果有節點綁定了對應事件,則觸發事件目標階段
找到事件觸及的最深節點冒泡階段
從最深節點按照捕獲的路徑進行返回,過程中如果有節點綁定了對應事件,則觸發事件
現代瀏覽器默認都會在冒泡階段觸發事件。
通過一個例子來簡單的感受一下。
<style>
.box {
display: flex;
align-items: center;
justify-content: center;
}
.size-100 {
width: 100px;
height: 100px;
background: #4caf50;
}
.size-200 {
width: 200px;
height: 200px;
background: chocolate;
}
.size-300 {
width: 300px;
height: 300px;
background: wheat;
}
</style>
<div class="box size-300">
<div class="box size-200">
<div class="box size-100">
</div>
</div>
</div>
<div class="result"></div>
<script>
var boxes = document.querySelectorAll('.box');
var result = document.querySelector('.result');
boxes.forEach(function(box) {
box.addEventListener('click', function() {
var el = document.createElement('p');
el.innerText = '現在觸發點擊事件的是' + this.className;
result.appendChild(el);
});
});
</script>
點擊后,觀察輸出可以發現,事件是點擊到的最深層次的節點開始向上執行的。
即從 size-100
到 size-200
到 size-300
,這就是冒泡的過程。
如果想讓事件在捕獲階段就執行,可以傳遞 addEventListener
方法第三個參數。
2. addEventListener 的第三個參數
addEventListener 的第三個參數用來決定事件在冒泡階段觸發還是在捕獲階段觸發,其為一個布爾值,傳遞 false
則事件會在冒泡階段觸發,傳遞 true
則會在捕獲階段觸發。
<style>
.box {
display: flex;
justify-content: center;
align-items: center;
}
.ele1 {
background: wheat;
width: 200px;
height: 200px;
}
.ele2 {
background: yellowgreen;
width: 100px;
height: 100px;
}
</style>
<div class="box ele1">
<div class="box ele2"></div>
</div>
<div class="result"></div>
<script>
var ele1 = document.querySelector('.ele1');
var ele2 = document.querySelector('.ele2');
var result = document.querySelector('.result');
function getElement(content) {
var el = document.createElement('p');
el.innerText = content;
return el;
}
ele1.addEventListener('click', function() {
result.appendChild(getElement('我是元素ele1'));
});
ele2.addEventListener('click', function() {
result.appendChild(getElement('我是元素ele2'));
});
</script>
根據默認瀏覽器事件是在冒泡階段觸發的規則,上述例子會先觸發子節點 .ele2
的事件,再觸發 .ele1
的事件。
如果想讓 .ele1
在捕獲階段就觸發事件,則在綁定事件的時候傳遞第三個參數為 true
即可。
<style>
.box {
display: flex;
justify-content: center;
align-items: center;
}
.ele1 {
background: wheat;
width: 200px;
height: 200px;
}
.ele2 {
background: yellowgreen;
width: 100px;
height: 100px;
}
</style>
<div class="box ele1">
<div class="box ele2"></div>
</div>
<div class="result"></div>
<script>
var ele1 = document.querySelector('.ele1');
var ele2 = document.querySelector('.ele2');
var result = document.querySelector('.result');
function getElement(content) {
var el = document.createElement('p');
el.innerText = content;
return el;
}
ele1.addEventListener('click', function() {
result.appendChild(getElement('我是元素ele1'));
}, true);
ele2.addEventListener('click', function() {
result.appendChild(getElement('我是元素ele2'));
});
</script>
這樣 .ele1
的事件就會在捕獲階段觸發。
3. 不符合W3C標準的事件流
早期的 IE 和 Netscape Navigator 是不符合標準的。
前者是使用事件冒泡流,后者使用事件捕獲流。
前面的章節有提到過 0級DOM事件
,其提供的綁定事件的方式是不能指定事件觸發的階段的,其原因是在那個階段下,還沒有現在制定的 DOM 事件流
。
當時并沒有統一的標準,0級DOM事件
也并不是一套官方出臺的標準,所有相關內功全部由瀏覽器廠商決定。
后來 W3C 很好的整合了這兩種模型,便有了現在的 DOM 事件流。
4. 冒泡的終點元素
這個問題其實經常會在面試中被問到,通常題目會是這樣的:
請描述一下事件捕獲和冒泡的具體流程
其實問的是事件從那個節點開始捕獲,然后到目標節點,最后又在哪個節點冒泡結束。
大部分面試者會回答 document
,其實根據事件對象的 path
屬性就可以得到答案。
path 屬性會返回事件冒泡的路徑,其最后是到 window
對象才停止的。
其實這點在標準中也有描述。
注意:path 屬性有兼容性問題,可以通過 can i use 確定??梢杂脴藴手械?composedPath 代替。
5. 小結
開發過程中很少會取改變事件觸發的階段。但是事件流的概念依然重要,因為很多時候要阻止事件冒泡。
理解了事件流,可以理解事件委托的原理,事件委托相關的內容可以參閱事件相關的性能優化。