JS阻止事件冒泡

2022-03-15 19:00:24
事件冒泡對於 DOM 操作有很大的幫助,但有時我們並不想要事件進行冒泡,例如下面所述的場景:

有一個表示學生基礎資訊的容器 ul,每個 li 元素都表示一個學生的基本資訊,單擊 li 元素會改變 li 的背景色以表示選中的標識。在每個 li 元素內部會有一個表示刪除的 button 按鈕,單擊 button 按鈕則會提示是否刪除,單擊確定則會刪除該元素。

為了單純地講解關於阻止事件冒泡的操作,實際的程式碼中我們省略掉了一些操作,方便大家瞭解重點:
<ul>
    <li>
        <p>姓名:小明</p>
        <p>學號:20180101</p>
        <button class="btn btn-default" id="btn">刪除</button>
    </li>
</ul>

<script>

    var li = document.querySelector('li');
    var btn = document.querySelector('#btn');

    li.addEventListener('click', function (event) {
        // 真實操作,使用console來代替
        console.log('單擊了li,做對應的處理');
    });

    btn.addEventListener('click', function (event) {
        // 真實操作,使用console來代替
        console.log('單擊了button,做對應的處理');
    });

</script>
上面程式碼在瀏覽器中執行後,當我們單擊 button 按鈕時,執行的結果如下所示:

單擊了 button,做對應的處理
單擊了 li,做對應的處理

由於事件冒泡的存在,在單擊 button 按鈕時,事件同樣會冒泡至父元素 li 上,因此兩個 click 事件都會被觸發。

但這並不是我們想要的結果,我們所期望的只有在單擊 li 時,才會觸發 li 的操作;只有在單擊 button 時,才觸發 button 的操作。這該怎麼去做呢?

我們只需要阻止事件冒泡就可以了,即在 button 按鈕的 click 事件中呼叫 event.stopPropagation() 函數。那麼在單擊 button 按鈕時,事件就只會在事件目標階段執行,而不會向上繼續冒泡至父元素 li 中,從而達到目的。

相應的 button 按鈕的 click 事件程式碼的更改如下,增加了對阻止事件冒泡的處理:
btn.addEventListener('click', function (event) {
    var event = EventUtil.getEvent(event);
    // 阻止事件冒泡
    event.stopPropagation();
    // 真實操作,使用console來代替
    console.log('單擊了button,做對應的處理');
});
這個時候再去單擊 button 按鈕,就會得到下面的結果:

單擊了 button,做對應的處理

通過結果可以看出,只觸發了 button 按鈕的 click 事件,並未觸發父元素 li 的 click 事件;而在單擊父元素 li 時,依然可以觸發 li 的 click 事件,輸出對應的結果。

此外,細心的同學可能會發現,在 event 物件中還存在一個 stopImmediatePropagation() 函數,從函數名可以看出它的作用也是用於阻止事件冒泡的,但是它和 stopPropagation() 函數有什麼區別呢?

兩者的區別主要體現在同一事件繫結多個事件處理程式的情況下:

  • stopPropagation() 函數僅會阻止事件冒泡,其他事件處理程式仍然可以呼叫。
  • stopImmediatePropagation() 函數不僅會阻止冒泡,也會阻止其他事件處理程式的呼叫。

我們沿用上面範例的程式碼,對 button 按鈕的 click 事件增加 3 個事件處理程式,在第二個事件處理程式中使用 stopPropagation() 函數來阻止事件冒泡。
var li = document.querySelector('li');
var btn = document.querySelector('#btn');
li.addEventListener('click', function (event) {
    // 真實操作,使用console來代替
    console.log('單擊了li,做對應的處理');
});

// 第一個事件處理程式
btn.addEventListener('click', function (event) {
    // 真實操作,使用console來代替
    console.log('button的第一個事件處理程式,做對應的處理');
});

// 第二個事件處理程式
btn.addEventListener('click', function (event) {
    var event = EventUtil.getEvent(event);
    // 阻止事件冒泡
    event.stopPropagation();
    // 真實操作,使用console來代替
    console.log('button的第二個事件處理程式,做對應的處理');
    });

// 第三個事件處理程式
btn.addEventListener('click', function (event) {
    // 真實操作,使用console來代替
    console.log('button的第三個事件處理程式,做對應的處理');
});
當我們單擊 button 按鈕後,得到的結果如下所示:

button 的第一個事件處理程式,做對應的處理
button 的第二個事件處理程式,做對應的處理
button 的第三個事件處理程式,做對應的處理

從結果可以看出,事件冒泡被阻止,li 上的事件處理程式並未觸發;繫結的 3 個事件處理程式都被觸發執行。

當我們將第二個事件處理程式中的 stopPropagation() 函數替換為 stopImmediatePropagation() 函數,又會得到什麼樣的結果呢?
// 第二個事件處理程式
btn.addEventListener('click', function (event) {
    var event = EventUtil.getEvent(event);
    // 阻止事件冒泡
    event.stopImmediatePropagation();
    // 真實操作,使用console來代替
    console.log('btn的第二個事件處理程式,做對應的處理');
});
當我們單擊 button 按鈕後,得到的結果如下所示:

button 的第一個事件處理程式,做對應的處理
button 的第二個事件處理程式,做對應的處理

從結果可以看出,事件冒泡被阻止,li 上的事件處理程式並未觸發;只有第一個和第二個事件處理程式被觸發執行,而第三個事件處理程式並未執行。