场景:react项目。
一般遇到这种问题网上的说法都是:
- 给点击开启下拉菜单的Dom元素方法中添加
e.stopPropagation()
阻止事件冒泡 - 再给document添加一个监听点击的事件:
document.addEventListener('click', this.offDropMenu, false)复制代码
document.addEventListener('click', this.offDropMenu, false)复制代码
可是这种一般只能适用于普通的下拉导航菜单,点击菜单就切换页面然后菜单就消失的这种情景,比如下面这种:
但是遇到这种复杂的菜单,当用户点击到菜单内部时不想让菜单消失该怎么办?
网上千篇一律的帖子又会说了,在下拉菜单的最外层Div再添加一个addEventListener监听点击事件,事件内只写一个e.stopPropagation()。
确实,点击菜单内部是不跳转了,但是里面的事件也全被阻止掉了呀!
点击都没反应了……
难道要我给每个子元素都添加阻止冒泡?
我思索片刻?,想到了一个很原始很暴力的方法,就是根据鼠标点击的位置来避开菜单?
(⚠️此代码可能会造成大神的不适~)
然后看着功能还凑合,也实现了业务需求,就那么放着了。
直到后来某一天,我发现了Dom元素有一个原生的方法contains()
就是为了验证知道某个节点是不是另一个节点的后代,返回一个布尔值:
document.documentElement.contains(document.body) // true
复制代码
document.documentElement.contains(document.body) // true
复制代码
这简直就是神器呀!刚好适用于这个场景,管你冒不冒泡:
但是还遇到一个问题,那就是e.stopPropagation()无法阻止冒泡。
参考React的事件系统,如果出于某些原因想使用浏览器原生事件,可以使用 nativeEvent 属性获取。
e.nativeEvent.stopImmediatePropagation() 替换掉e.stopPropagation() 可以达到效果。
下面进入探索环节,经查阅资料,得出以下结论: 1. React为了提高效率,把事件都委托给了document,所以 e.stopPropagation() 并非是不能阻止冒泡,而是等他阻止冒泡的时侯,事件已经传递给document了,没东西可阻止了。可以通过在document.body上绑定 alert(3),直观的了解这一点,3 是优先于 1 弹出的。 2. e.stopPropagation()不行,浏览器支持一个好东西,e.stopImmediatePropagation() 他不光阻止冒泡,还能阻止在当前事件触发元素上,触发其它事件。这样即使你都绑定到document上也阻止不了我了吧。 3. 这样做还不行,React对原生事件封装,提供了很多好东西,但也省略了某些特性。e.stopImmediatePropagation() 就是被省略的部分,然而,他给了开口:e.nativeEvent ,从原生的事件对象里找到stopImmediatePropagation(),完活。
stopImmediatePropagation和stopPropagation的区别
- stopImmediatePropagation :
阻止事件流中当前节点的和所有后续节点的事件监听器的执行。即影响当前结点的事件监听器。
- stopPropagation:
举个?:给一个元素添加三个点击事件(注册顺序,就是代码的执行顺序)
- 如果是
e.stopPropagation()
,执行结果为依次弹出 1,2,2.1,3。因为不会影响到当前节点的事件 - 若是
e.stopImmediatePropagation()
,结果则为 1,2,2.1。
总结:只是一个很好用的contains()
方法,记得最开始学习的时候是看过这个属性,归根到底还是自己基础没打牢!