【20190415】JavaScript-事件流與stopPropagation()、stopImmediatePropagation()的誤區解析

這兩天仔細看了一下MDN上關於事件流機制和相關方法的文檔,發現有個很大的誤區。過去我一直覺得stopPropagation()就是用來阻止事件冒泡的,甚至不少博客和菜鳥教程上都是這樣寫的。但實際上文檔中對stopPropagation()的解釋是:函數

The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phasesspa

阻止當前事件在捕獲和冒泡階段的進一步傳播,個人理解就是阻止當前事件的繼續傳播,不只阻止冒泡,也阻止捕獲和目標階段,只在於你是在哪一個階段調用stopPropagation()的。code

事件流過程

一個事件被觸發,它的事件流包括三個階段:捕獲、目標、冒泡。捕獲階段的傳播路徑是:window對象→document→HTML→body→...→target的直接父元素,從最外層最不具體的元素層層傳遞到最裏層具體的元素。目標階段就是事件目標元素上發生。冒泡階段與捕獲階段的傳播路徑正好相反:target→...→body→HTML→document→window,通常會將目標階段看做冒泡階段的一部分。對象

事件的傳播路徑在事件開始傳播以前就已經肯定,若是在傳播過程當中DOM結構發生變化,那麼也會按照根據原來的結構而生成的傳播路徑進行傳播。blog

Graphical representation of an event dispatched in a DOM tree using the DOM event flow

 

添加事件監聽:addEventListener(eventType, function, useCapture)教程

useCapture爲true,表示該監聽器監聽捕獲階段的事件,useCapture爲false,則監聽冒泡階段的事件。若是是在事件目標元素上添加的事件監聽,那麼無論useCapture設置爲true仍是false都能監聽到,並且此時的事件階段eventPhase爲2。事件

簡單來講就是爲某個元素添加一個事件監聽器,當有事件傳播到該元素時,若是事件處於捕獲階段,eventPhase=1,那麼設置了useCapture爲true的監聽器就會響應。若是事件傳播到該元素時處於冒泡階段,eventPhase=3,那麼設置了useCapture爲false的監聽器響應。若是事件傳播到該元素時處於目標階段,eventPhase=2,那麼無論是設置了true仍是false的監聽器都能響應。ip

下面是個例子:文檔

<div class="first">first
    <div class="second">
        <label>click me
            <input type="checkbox" class="target">
        </label>
    </div>
</div>
<script>
let div1=document.getElementsByClassName('first')[0], div2=document.getElementsByClassName('second')[0], target=document.getElementsByClassName('target')[0];
div1.addEventListener(
'click',(e)=>{ alert('first: '+e.eventPhase+'1'); },true); div1.addEventListener('click',(e)=>{ alert('first: '+e.eventPhase+'2'); },true); div1.addEventListener('click',(e)=>{ alert('first: '+e.eventPhase); },false); div2.addEventListener('click',(e)=>{ alert('second: '+e.eventPhase); },true); div2.addEventListener('click',(e)=>{ alert('second: '+e.eventPhase); },false); target.addEventListener('click',(e)=>{ alert('target: '+e.eventPhase); },true); target.addEventListener('click',(e)=>{ alert('target: '+e.eventPhase); },false); </script>

點擊CheckBox後,依次彈出「first:11」、「first:12」、「second:1」、「target:2」、「target:2」、「second:3」、「first:3」。get

stopPropagation()

那麼stopPropagation()方法就比較好理解了,若是在某個階段在事件監聽函數裏調用了事件的stopPropagation()方法,那麼該事件後續的傳播過程都會被阻止,不管是捕獲仍是處於目標或者冒泡。

好比你在捕獲階段監聽到事件,而且調用了stopPropagation()方法,那麼後續的在子節點上的捕獲過程(若是還有的話,若是是在捕獲過程的最後一個節點上調用,那麼就是直接阻止將要進行的目標過程)、處於目標過程、冒泡過程都會被阻止。若是在處於目標階段調用,那麼後續的冒泡過程被阻止。若是在冒泡過程調用,後續的在其餘節點上的冒泡傳播被阻止。

這裏要注意的是,stopPropagation()是以元素節點間的傳播爲單位,若是後續的傳播過程當中事件須要從一個元素節點流轉到它的父節點或子節點,那麼stopPropagation()將會阻止這樣的過程。也就是說在一個元素節點內,它是不起做用的,若是在調用stopPropagation()的元素節點內還存在其餘事件監聽,而且與調用stopPropagation()的監聽器的響應規則是同樣的,那麼這些其餘的監聽依然會按順序響應。

例如在上面的例子中,first有兩個捕獲階段的事件監聽器,在第一個裏調用stopPropagation(),那麼第二個監聽依然會響應,可是後續的捕獲、目標和冒泡過程都被阻止了。所以最終只會彈出first的兩個彈窗:「first:11」、「first:12」;

stopImmediatePropagation()

而stopImmediatePropagation()在調用後就直接阻止事件的後續傳播過程了,包括同一個元素節點內的過程。若是在調用stopImmediatePropagation()的元素節點上還有其餘事件監聽,那麼在調用stopImmediatePropagation()後,其餘事件監聽也不會響應。

仍是以上面的例子,若是在first的第一個捕獲監聽器裏調用stopImmediatePropagation(),那麼最終只會有一個彈窗:「first:11」。

若是在target的捕獲監聽器裏調用stopImmediatePropagation(),彈窗結果是:「first:11」、「first:12」、「second:1」、「target:2」。

preventDefault()

不少人把preventDefault()和阻止冒泡、捕獲搞混,實際上preventDefault()和事件傳播過程沒有關係,它的做用是阻止事件的默認行爲,例如a標籤在click事件後的默認行爲是連接到新的頁面,checkbox的click事件默認行爲是將修改的選中狀態生效。preventDefault()的做用則是阻止這些默認行爲,它不會影響事件的傳播,並且不管在事件的哪一個階段調用preventDefault(),都能阻止默認行爲。

相關文章
相關標籤/搜索