事件流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中的事件修饰符