詳細剖析 event 幾個基本點

事件流html

當在某個DOM元素上觸發事件之後,DOM是如何派發事件並尋找監聽器的呢?這個派發過程就是事件流。chrome

事件從Document(有的瀏覽器是window,總之是最外層根元素)開始,按照物理層級結構,往下逐級捕獲到事件,直到到達目標元素target,再原路冒泡返回。瀏覽器

這個路程中,會按順序執行全部綁定的對應的監聽函數。函數

先擺個從網上找的圖:給一個整體映像。
-1849443303.jpeg
圖0-1性能

抽絲剝繭spa

前提:
HTML:3d

<body>
    <div id="c0">
        c0
        <div id="c1">c1</div>
        <div id='c2'>c2
            <div id="c21">c21</div>  
            <div id="c22">c22</div>
            <div id="c23">c23</div>     
        </div>
    </div> 
</body>
<style>
    #c0{margin:10px;padding:10px;background-color: palegoldenrod; position: relative;}
    #c1{margin:20px;background:rgb(165, 252, 194);width:100px;height:30px;}  
    #c2{margin:20px;background:plum;}  
    #c21,#c22,#c23,#c24,#c25{margin:10px;background:peru;width:100px;height:30px;}  
    #c22,#c24{background:gold;} 
    /* #c2{position:absolute;top:100px;} 
    #c23{margin-bottom:0} */
    /* #c2{
        position: relative;
    }
    #c21{
        z-index:2;
    }
    #c21,#c22,#c23{
        position:absolute;
    } */
    /* #c22{
        position: absolute;
        top:20px;
        left:40px;
    }
    #c21{
        z-index:2
    } */
</style>
<script>
    var divs = Array.from(document.getElementsByTagName('div'))
    divs.forEach(element => {
        element.addEventListener('click', function (event) {
            console.log('click:target='+event.target.getAttribute('id')+",currentTarget="+event.currentTarget.getAttribute('id'))
        }, false)
    });
</script>

event對象
監聽器在執行時,會傳一個event對象給回調函數。event對象裏有不少屬性,用以傳遞事件的相關重要信息。其中,有一個target,一個currentTarget,部分事件還有relatedTarget指針

一、target是誰?
event.target:在哪一個DOM元素上觸發了事件。它必定是物理空間上能感知該事件,最深層的,離用戶最近的元素code

如下實驗均在chrome上操做。htm

只在c0上綁定監聽click事件,而後依次點擊不一樣區域。(默認useCapture = false,這個後面會分析)
clipboard.png
圖1-1
target就是那個直接被點擊的DOM元素。

1.1 最深層次 不用講了。
1.2 物理空間
#c2{position:absolute;top:100px;}
clipboard.png
這些區域並不會觸發點擊事件。
彷佛也能理解爲可視區域的意思?

1.3.離用戶最近
1) 對元素position作個修改:
#c2{position: relative;}
#c22{position: absolute;top:20px; left:40px;}
clipboard.png
互換下C21,C22 在html中的先後位置,結果與上圖徹底一致。

2) 將C2的子元素所有position:absolute,徹底重疊
#c21,#c22,#c23{position:absolute;}
clipboard.png
離用戶最近的取決於子元素在html中的順序。最後的,在最上面。

3) c23,c22互換順序,最後的c22就在最上面成爲target
clipboard.png

4) 給c21添加index
#c21{z-index:2}
clipboard.png

因此:優先級:相同position下,z-index > 元素順序。
position > z-index > 元素順序

二、currentTarget 是誰?
event.currentTarget: target所在物理層級DOM鏈上,當前eventListener所在的元素。
通俗講:就是事件流當前所在的對象,也就是註冊了對應事件listener的元素。

當將全部元素都綁定上click的listener。 4次點擊,右邊分別輸出4塊
clipboard.png
圖2-1
與圖1-1相比,相同次數點擊。兩點區別:
(1)、執行次數變多了。以前只有c0監聽了click,如今每一個元素都監聽了click。事件流會執行沿路全部監聽函數。
如何區分執行了誰的監聽函數?currentTarget就是當前listener所在DOM元素
(2)、因此圖2-1的currentTarget再也不和圖1-1同樣始終是c0

再仔細看c22點擊,觸發監聽函數的順序
由子及父:c22->c2->c0
是否是對事件有點初步理解?

三、冒泡
useCapture = false

事件流嚴格講,前後有3個階段:捕獲階段,進入目標階段,冒泡階段。
冒泡就是指冒泡階段。圖0-1的 4->5->6->7步

target.addEventListener(type, listener, useCapture);
要理解冒泡,必須理解監聽器註冊函數addEventListener的第三個參數:useCapture。

useCapture:是否在捕獲階段執行監聽函數。默認值爲false,即默認是在冒泡階段執行監聽函數,因此,在圖2-2中的順序是由子及父。

四、捕獲
useCapture = true
clipboard.png
useCapture設置爲true之後。根據currentTarget,可見函數執行順序,是由父及子。這就是捕獲階段。

每一次事件在不被攔截的狀況下,都會按照物理層理結構,走完捕獲、冒泡整個過程。(部分事件除外)

好,一鼓作氣,理解了事件流了吧。
更多event的屬性,見:https://developer.mozilla.org/zh-CN/docs/Web/API/Event

咱們再深刻一些。

五、阻止冒泡
能夠理解爲:截斷事件流。

e.stopPropagation(),IE則是使用e.cancelBubble = true

5.1 useCapture = false時
clipboard.png
useCapture = false,各個listener元素都阻止了冒泡, target === currentTarget
冒泡階段,阻止冒泡,中止後續事件流。

5.2 useCapture = true時
clipboard.png
c0:useCapture = true,永遠在c0捕獲時就執行監聽函數。
捕獲階段,阻止子元素繼續捕獲事件,中止後續事件流,包括冒泡階段。

六、event.stopImmediatePropagation()
若是有多個相同類型事件的事件監聽函數綁定到同一個元素,當該類型的事件觸發時,它們會按照被添加的順序執行。若是其中某個監聽函數執行了 event.stopImmediatePropagation() 方法,則當前元素剩下的監聽函數將不會被執行。

七、分析幾個易混淆的mouseEvent

MouseEvent 派生自 UIEventUIEvent 派生自 Event
因此它集合了3個類的屬性。
clipboard.png
詳見:https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent

7.1 mousemove
mousemove,在元素內滑動觸發。

先只打印target。
clipboard.png
target就是當前鼠標所在最深層次離用戶最近的DOM元素。

同一個target觸發事件背後,其實執行了多個監聽器。若是打印currentTarget的話,console面板會很長了。
clipboard.png
冒泡模式,currentTarget由子及父,循環往復

c21裏的滑動效果,就是下圖:
clipboard.png

因此若是在一條DOM鏈上,綁定太多mousemove監聽器,開銷是很大的。通常都會對mousemove作節流:以必定的時間間隔來執行move事件。還有個概念叫防抖:始終延遲執行直到一段時間內再也不觸發時才執行一次。根據個人清晰描述,你是否能本身寫出節流和防抖函數呢?

7.2 mouseover 和 mouseenter

7.2.1 mouseover
進入目標元素可視區域就觸發1次。能夠理解爲:滑過可視區域。
哪怕進入子元素後再從子元素進入元素,也會再觸發1次mouseover事件
與之相對應的事件爲 mouseout
clipboard.png

若是是
c0:{position:relative}
c2:{position:absolute;top:100px;}
clipboard.png
圖7-2-1-2

7.2.2 mouseenter
進入元素物理空間時觸發1次。
只要在元素物理空間內,這個元素都不會再次觸發,哪怕進去子元素再出來。
與之相對應的事件爲 mouseleave
clipboard.png

不是說事件不阻止就會走完捕獲、冒泡全程嗎?
爲何看起來好像沒有冒泡,並無出現相似target=c1,currentTarget=c0的記錄

對的。mouseenter與mousemove最大的區別就是:mouseenter不會冒泡。而是會向層次結構的每個元素髮送一個mouseenter事件。因此開銷很大。

=mouseenter.png
(mouseenter事件)觸發時,會向層次結構的每一個元素髮送一個mouseenter事件。當指針到達文本時,此處將4個事件發送到層次結構的四個元素。

=mouseover.png
一個單獨的mouseover事件被髮送到DOM樹的最深層元素,而後它將層次結構向上冒泡,直到它被處理程序取消或到達根目錄。

因此一旦進入enter一個DOM元素的物理空間,在離開leaver以前,不會再次觸發該DOM的enter事件。而mouserenter又不會冒泡,這就解釋了圖7-2-1-2爲何每一個target只有一條記錄,每一個currentTarget也只有一條記錄

那鼠標若是反過來走呢?畢竟c23和c2的下邊緣重疊的。
clipboard.png
中間那一步,c0 -> c23。會直接產生2條記錄,表示進入了c23,c2,並且經過currentTarget發現,並不是冒泡觸發。往各個層級結構發送事件,才能體現enter這個詞的意思。仔細體會下。
細心的同窗也發現:mouseenter的 target === currentTarget

另外,請注意,事件派發順序。當默認useCapture = false時,mouseenter父級監聽器會優先執行
若是useCapture = true:
image.png
當useCapture = true時,mouseenter保持了捕獲階段的事件流

另外一個例子(useCapture = false)
c0:{position:relative}
c2:{position:absolute;top:100px;}
clipboard.png

7.3 mouseout 與 mouseleave

7.3.1 mouseout:劃出可視區域時觸發1次。
clipboard.png

7.2.2 mouseleave:離開元素。
mouseleave同mouseenter同樣,不會冒泡,離開幾個元素,發幾個事件。
clipboard.png

clipboard.png
當離開它們時,一個mouseleave事件被髮送到層次結構的每一個元素。當指針從文本移動到這裏表示的最外面的div以外的區域時,這裏4個事件會發送到層次結構的四個元素。

clipboard.png
一個單一的鼠標事件mouseout被髮送到DOM樹最深的元素,而後它冒泡層次,直到它被處理程序取消或到達根。

八、VUE中的事件修飾符

  • .stop : 等價於e.stopPropagation() 阻止冒泡
  • .prevent:等價於e.preventDefault() 阻止默認動做
  • .capture:等價於捕獲模式
  • .self : 等價於target === curentTarget:只當在 event.target 是當前元素自身時觸發處理函數。
  • .once :只觸發一次。
  • .passive:滾動事件的默認行爲 (即滾動行爲) 將會當即觸發。尤爲可以提高移動端的性能
相關文章
相關標籤/搜索