當我們同時為父元素和子元素綁定相同事件時可能會發現子元素事件被觸發時,父元素同時也會被觸發。
如下範例,div
和 button
都綁定了 click
事件,為什麼我們點選按鈕時會觸發 div
的 click
事件呢?
<div style="background-color: lightgoldenrodyellow;">
<button id="button">點選</button>
</div>
<script>
var button = document.getElementById('button');
button.addEventListener('click', function(){
console.log('點按鈕');
});
var div = document.querySelector('div');
div.addEventListener('click', function(){
console.log('點 div');
});
</script>
以白話一點的方式解釋,div
是 button
的父元素,也就是說當我們點選按鈕時,也同樣會點選到包裹著按鈕的 div
,而真正的原因其實是「事件的傳遞階段」—— 捕獲和冒泡,所造成的。
在 JS 事件監聽發生時,事件會沿著 HTML 結構從 Window 一層一層往下尋找目標,最終找到事件設定的目標後再依序逐層往回(外)傳遞,這個流程就是事件的三階段,捕獲階段、目標階段、及冒泡階段。
[圖片源自:W3C]
由上圖我們可以看出當使用者點擊 td
時,會從根節點一層一層往下尋找目標,這個過程稱為 Capture Phase (捕獲階段),事件會一路傳到直到找到目標 (也就是 td
),找到目標後會再返回一層一層往上,這個過程稱為 Bubbling Pase
(冒泡階段),而在這個階段若元素也有 click
事件時,也同樣會被觸發。
事件冒泡的特性是預設會在冒泡過程中觸發目標元素的上級元素之相同事件,也就是說當我們綁定許多 click
事件時,可能會因為事件冒泡而同時處發多個我們不需要的事件。
範例:
<style>
.first {
background-color: skyblue;
width: 500px;
height: 500px;
}
.second {
background-color: pink;
width: 350px;
height: 350px;
}
.third {
background-color: yellow;
width: 200px;
height: 200px;
}
</style>
<div class="first">
<div class="second">
<div class="third"></div>
</div>
</div>
<script>
const one = document.querySelector('.first');
const two = document.querySelector('.second');
const three = document.querySelector('.third');
one.addEventListener('click', function(){
alert("最大的");
});
two.addEventListener('click', function(){
alert("第二大的");
});
three.addEventListener('click', function(){
alert("最小的");
});
</script>
在上面的範例中,我們寫了三個互相嵌套的 div
和相對應的 event listener
,當點擊最小的 div
時,會在冒泡的過程中依序觸發第二個及最大的 div
的點擊事件,而若想避免這件事情,我們就需要阻止冒泡。
語法為:event.stopPropagation();
,只要在不需要冒泡的事件中加入這行就可以成功阻止冒泡。
範例:阻止冒泡
<div class="first">
<div class="second">
<div class="third"></div>
</div>
</div>
<script>
const one = document.querySelector('.first');
const two = document.querySelector('.second');
const three = document.querySelector('.third');
one.addEventListener('click', function(e){
event.stopPropagation();
alert("最大的");
});
two.addEventListener('click', function(e){
event.stopPropagation();
alert("第二大的");
});
three.addEventListener('click', function(e){
event.stopPropagation();
alert("最小的");
});
</script>
除了冒泡之外,在事件中還有許多預設行為,像是 <a>
連結的跳轉、<form>
表單提交等等,而有時候我們並不想要觸發事件的預設行為,這時候就可以利用 event.preventDefault()
進行取消。
例如下面範例,當我們想阻止跳轉行為時,可以在 click
事件中加入 e.preventDefault()
const link = document.querySelector('a');
link.addEventListener('click',function(e){
e.preventDefault();
console.log('不會跳轉');
});