搞懂dom的事件机制
码农天地 -序
最近遇到一个问题,当在一个dom元素中绑定了一个事件回调,但是触发该事件后事件的回调却未被执行,可能是什么原因呢?
经过一顿胡思乱想和对事件捕获冒泡机制的一番复习之后,总结出了两个原因
事件在冒泡过程中,子元素阻止了该事件向上冒泡事件在捕获过程中,父元素阻止了该事件向下捕获实在想不出还有什么其他原因了,所以发这篇博客也是希望有懂的大神给小弟指点迷津
事件传播机制dom事件的事件源(即在回调函数中通过event.target
得到的dom元素),我们把它称作事件的目标元素,在触发一个事件前后,目标元素只会经历一个阶段 -- 目标阶段,而目标元素的所有父元素及其祖先元素都会经历两个阶段 -- 捕获阶段和冒泡阶段。
假如现在有一个节点树是这样的:
document -> 祖先n -> ... -> 父亲 -> 目标元素
当触发目标元素的事件时,粗略地描述一下各个元素经历的过程就是:
document捕获 -> 祖先n捕获 -> ... -> 父亲捕获 -> 目标触发事件 -> 父亲冒泡 -> ... -> 祖先n冒泡 -> document冒泡
即捕获阶段是从上到下,而冒泡阶段是从下到上。如果我们使用element.addEventListener(callBack)
为一个元素添加事件回调时,该回调默认会在目标阶段(如果它是事件源)或冒泡阶段(如果它是事件源的祖先)触发。
第一个原因已经很清晰了,只要在子元素的回调函数中使用stopPropagation阻止该事件向上冒泡即可。
如果我们进一步了解addEventListener这个方法,我们就会发现除了回调函数以外,还可以传入一个options
对象参数,来配置如何处理该事件。
const options = {
capture: Boolean, // 表示 `listener` 会在事件的捕获阶段到达该元素时触发。
once: Boolean, // 表示 `listener 在添加之后最多只调用一次`
passive: Boolean // 表示 `listener` 永远不会调用 `preventDefault()`。
}
我们注意到这个对象有一个属性叫capture
,如何将它设置为true
,表示回调函数会在该元素捕获到事件时被调用,并且stopPropagation不仅能阻止事件向上冒泡,也能阻止事件向下捕获,因此第二个原因也可以做到了,只要父元素设置事件捕获阶段触发的回调,并且阻止事件的向下捕获。(我们常见的element.addEventListener(callBack, true)
也是一种设置回调在捕获阶段执行的方式)
上面说到,事件源是没有捕获阶段或是冒泡阶段的,所以在事件源的监听中设置capture
是无效的,如果你想要事件源在监听到某个事件时阻止触发该元素上绑定的其他回调,那么你可能需要使用stopImmediatePropagation。
如果想要加深一下事件捕获和冒泡的机制,可以看看这个实际演示
点这里查看实例的源代码
最后看到这里了,不妨再思考一个问题,如果在实际开发过程中发现某个元素的事件监听回调由于子元素的阻止冒泡而导致无法触发,如何快速定位出问题的子元素?