事件流html
當在某個DOM元素上觸發事件之後,DOM是如何派發事件並尋找監聽器的呢?這個派發過程就是事件流。chrome
事件從Document(有的瀏覽器是window,總之是最外層根元素)開始,按照物理層級結構,往下逐級捕獲到事件,直到到達目標元素target,再原路冒泡返回。瀏覽器
這個路程中,會按順序執行全部綁定的對應的監聽函數。函數
先擺個從網上找的圖:給一個整體映像。
圖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,這個後面會分析)
圖1-1
target就是那個直接被點擊的DOM元素。
1.1 最深層次 不用講了。
1.2 物理空間#c2{position:absolute;top:100px;}
這些區域並不會觸發點擊事件。
彷佛也能理解爲可視區域的意思?
1.3.離用戶最近
1) 對元素position作個修改:#c2{position: relative;}
#c22{position: absolute;top:20px; left:40px;}
互換下C21,C22 在html中的先後位置,結果與上圖徹底一致。
2) 將C2的子元素所有position:absolute,徹底重疊#c21,#c22,#c23{position:absolute;}
離用戶最近的取決於子元素在html中的順序。最後的,在最上面。
3) c23,c22互換順序,最後的c22就在最上面成爲target
4) 給c21添加index#c21{z-index:2}
因此:優先級:相同position下,z-index > 元素順序。
position > z-index > 元素順序
二、currentTarget 是誰?
event.currentTarget: target所在物理層級DOM鏈上,當前eventListener所在的元素。
通俗講:就是事件流當前所在的對象,也就是註冊了對應事件listener的元素。
當將全部元素都綁定上click的listener。 4次點擊,右邊分別輸出4塊
圖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
useCapture設置爲true之後。根據currentTarget,可見函數執行順序,是由父及子。這就是捕獲階段。
每一次事件在不被攔截的狀況下,都會按照物理層理結構,走完捕獲、冒泡整個過程。(部分事件除外)
好,一鼓作氣,理解了事件流了吧。
更多event的屬性,見:https://developer.mozilla.org/zh-CN/docs/Web/API/Event
咱們再深刻一些。
五、阻止冒泡
能夠理解爲:截斷事件流。
e.stopPropagation(),IE則是使用e.cancelBubble = true
5.1 useCapture = false時
useCapture = false,各個listener元素都阻止了冒泡, target === currentTarget
冒泡階段,阻止冒泡,中止後續事件流。
5.2 useCapture = true時
c0:useCapture = true,永遠在c0捕獲時就執行監聽函數。
捕獲階段,阻止子元素繼續捕獲事件,中止後續事件流,包括冒泡階段。
六、event.stopImmediatePropagation()
若是有多個相同類型事件的事件監聽函數綁定到同一個元素,當該類型的事件觸發時,它們會按照被添加的順序執行。若是其中某個監聽函數執行了 event.stopImmediatePropagation() 方法,則當前元素剩下的監聽函數將不會被執行。
七、分析幾個易混淆的mouseEvent
MouseEvent 派生自 UIEvent,UIEvent 派生自 Event
因此它集合了3個類的屬性。
詳見:https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent
7.1 mousemove
mousemove,在元素內滑動觸發。
先只打印target。
target就是當前鼠標所在最深層次離用戶最近的DOM元素。
同一個target觸發事件背後,其實執行了多個監聽器。若是打印currentTarget的話,console面板會很長了。
冒泡模式,currentTarget由子及父,循環往復
c21裏的滑動效果,就是下圖:
因此若是在一條DOM鏈上,綁定太多mousemove監聽器,開銷是很大的。通常都會對mousemove作節流:以必定的時間間隔來執行move事件。還有個概念叫防抖:始終延遲執行直到一段時間內再也不觸發時才執行一次。根據個人清晰描述,你是否能本身寫出節流和防抖函數呢?
7.2 mouseover 和 mouseenter
7.2.1 mouseover
進入目標元素可視區域就觸發1次。能夠理解爲:滑過可視區域。
哪怕進入子元素後再從子元素進入元素,也會再觸發1次mouseover事件
與之相對應的事件爲 mouseout
若是是c0:{position:relative}
c2:{position:absolute;top:100px;}
圖7-2-1-2
7.2.2 mouseenter
進入元素物理空間時觸發1次。
只要在元素物理空間內,這個元素都不會再次觸發,哪怕進去子元素再出來。
與之相對應的事件爲 mouseleave
不是說事件不阻止就會走完捕獲、冒泡全程嗎?
爲何看起來好像沒有冒泡,並無出現相似target=c1,currentTarget=c0的記錄
對的。mouseenter與mousemove最大的區別就是:mouseenter不會冒泡。而是會向層次結構的每個元素髮送一個mouseenter事件。因此開銷很大。
(mouseenter事件)觸發時,會向層次結構的每一個元素髮送一個mouseenter事件。當指針到達文本時,此處將4個事件發送到層次結構的四個元素。
一個單獨的mouseover事件被髮送到DOM樹的最深層元素,而後它將層次結構向上冒泡,直到它被處理程序取消或到達根目錄。
因此一旦進入enter一個DOM元素的物理空間,在離開leaver以前,不會再次觸發該DOM的enter事件。而mouserenter又不會冒泡,這就解釋了圖7-2-1-2爲何每一個target只有一條記錄,每一個currentTarget也只有一條記錄
那鼠標若是反過來走呢?畢竟c23和c2的下邊緣重疊的。
中間那一步,c0 -> c23。會直接產生2條記錄,表示進入了c23,c2,並且經過currentTarget發現,並不是冒泡觸發。往各個層級結構發送事件,才能體現enter這個詞的意思。仔細體會下。
細心的同窗也發現:mouseenter的 target === currentTarget
另外,請注意,事件派發順序。當默認useCapture = false時,mouseenter父級監聽器會優先執行。
若是useCapture = true:
當useCapture = true時,mouseenter保持了捕獲階段的事件流
另外一個例子(useCapture = false)c0:{position:relative}
c2:{position:absolute;top:100px;}
7.3 mouseout 與 mouseleave
7.3.1 mouseout:劃出可視區域時觸發1次。
7.2.2 mouseleave:離開元素。
mouseleave同mouseenter同樣,不會冒泡,離開幾個元素,發幾個事件。
當離開它們時,一個mouseleave事件被髮送到層次結構的每一個元素。當指針從文本移動到這裏表示的最外面的div以外的區域時,這裏4個事件會發送到層次結構的四個元素。
一個單一的鼠標事件mouseout被髮送到DOM樹最深的元素,而後它冒泡層次,直到它被處理程序取消或到達根。
八、VUE中的事件修飾符