🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## js事件冒泡 事件是监听在某个DOM元素上的,但是js的DOM事件有捕获和冒泡的机制,所以事件处理不是我们想的那样简单。 由于存在捕获和冒泡,所以事件的触发元素(目标源)不一定是当前的监听元素。于是就有一些问题,本文要解决的就是将这些问题整理叙述清楚。 **关键点:** - event 事件对象 - 目标(源) target - 当前目标(源) currentTarget - 元素 element(当前捕获的元素和事件源目标元素) **要解决两个问题:** 1. 阻止事件从当前元素上往上层冒泡 2. 识别出当前事件的目标源,判断是不是冒泡上来的事件 ~~~javascript // 二级菜单单击事件 $(document).on('click', '.Level_2_menu__btn', function(e) { var e = e || window.event; var target = e.tagName || e.srcElement || e.toElement || e.target; var className = target.className || $(target).attr('className'); if (className.indexOf('Level_2__button') == -1) { return false; } // alert(target.className); }); ~~~ * * * * * ### 参考 [JS事件:target与currentTarget区别 - wkylin - 博客园](https://www.cnblogs.com/wkylin/archive/2011/08/25/2153538.html) > 上面的示例中,当在outer上点击时,e.target与e.currentTarget是一样的,都是div;当在inner上点击时,e.target是p,而e.currentTarget则是div。 [事件 · 小程序](https://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxml/event.html)(参见:target/currentTarget) > target:触发事件的组件的一些属性值集合;currentTarget:当前组件的一些属性值集合。 [jQuery之防止冒泡事件 - TBHacker - 博客园](http://www.cnblogs.com/jiqing9006/archive/2012/09/11/2679831.html) >return false 会把浏览器默认事件和冒泡干掉,ev.preventDefualt(); 可以单独阻止默认事件,ev.stopPropagation() 可以单独阻止冒泡 [JQuery中阻止事件冒泡方式及其区别 - JeamKing的专栏 - CSDN博客](http://blog.csdn.net/JeamKing/article/details/5332328/) [JS事件委托的原理和应用 - 追梦子 - 博客园](http://www.cnblogs.com/pssp/p/5253284.html) [JS Event事件 - 追梦子 - 博客园](http://www.cnblogs.com/pssp/p/5277749.html) [走进javascript——DOM事件 - 追梦子 - 博客园](http://www.cnblogs.com/pssp/p/6382874.html) [addEventListener和on的区别 - 追梦子 - 博客园](http://www.cnblogs.com/pssp/p/5196716.html) [JQuery阻止冒泡事件on绑定中异常情况分析 - 胖逆的嘟嘟 - 博客园](http://www.cnblogs.com/tengj/p/4794947.html) [解析Javascript事件冒泡机制 - 我的程序人生 - CSDN博客](http://blog.csdn.net/luanlouis/article/details/23927347) [javascript冒泡事件的意义及如何阻止冒泡事件 - u012169411的专栏 - CSDN博客](http://blog.csdn.net/u012169411/article/details/16804233) [游览器中有哪些事件会冒泡?](https://segmentfault.com/q/1010000000687977) [JS事件流与DOM事件处理程序](https://www.toutiao.com/i6392562710684369410/) [事件捕获和事件冒泡什么意思?-coder分享的回答-悟空问答](https://www.wukong.com/answer/6492960953607389453/?iid=12619555732&app=news_article&share_ansid=6492960953607389453&wxshare_count=1&tt_from=weixin&utm_source=weixin&utm_medium=toutiao_android&utm_campaign=client_share) * * * * * ### 总结 **这些文章都是讲如何阻止事件往上冒泡,也就是解决的是第一个问题,并没有说到如何解决第二个问题,“怎么防止事件是冒泡上来的?”**,其实要解决第二个问题需要自己判断目标源,或者使用事件代理,$.on(),这其实是将事件监听(代理)在父元素上面,内部也是利用了冒泡的机制,并判断目标源,实现事件代理的。 为了不必要的麻烦(事件触发有可能是由被真正触发的子元素冒泡上来的),一般请尽量使用事件代理on来绑定事件(**三个参数**,两个参数的话就和 $("#id").click() 差不多,避免不了事件可能是由冒泡上来的了,还得自己判断)。 * * * * * ### 事件触发时机的探索 事件有三种触发时机: 1. 直接触发,事件源目标上面的事件。 2. 接收到冒泡时是否能被触发。 3. 捕获阶段是否能被触发。2和3只能同时存在一种,1都存在。所以实际的情况都是两种触发时机,1,2 和 1,3。不过捕获用得少,并且默认事件定义都是冒泡,所以大多情况,元素的事件都是 1,2 (直接触发和冒泡) 两种触发时机。 ```javascript $('#id').click(function(event){ // 注意这里的event和window.event是不同的,前者是Jq传进来的实参,后者是全局变量,事件发生时会产生的,每个事件现场都有一个这个全局变量。 var event = event ? event : window.event是不同的,前者是Jq传进来的,后者是全局的,事件发生时会产生的。; // 阻止向上冒泡(即使当前不为事件目标源时也会阻止冒泡,即别人的泡也是可以阻止的,不过要知道泡是直接往上的,是从目标源往上冒的) event.stopPropagation(); // 阻止默认事件,比如表单提交、防止链接打开 URL event.preventDefault(); // 事件目标源 var target = event.srcElement ? event.srcElement : event.target; // 当前事件触发的元素 var that = this; // target不一定等于this,this可能是冒泡上来的事件,target是直接触发的目标源 // 事件源目标元素的标签名 a console.log( target.localName); // 事件源目标元素的类名 console.log(target.className); }); // 注意:这是等同的 // return false; 《=》 event.stopPropagation();event.preventDefault(); ``` **(重点:别人的泡也是可以阻止的?)** 待仔细验证证明 * * * * * 默认事件是最后才被触发的事件,所以这样能阻止回车提交: ```javascript // 禁止回车提交 $(document).on('keydown', 'form', function() { if (event.keyCode == 13) { return false; } }); ``` * * * * * ### 冒泡实战 分析Bootstrap的下拉组件dropdown还有看云的下拉组件: ![](https://box.kancloud.cn/7439a1292ac28ec690a375e385a52f10_409x471.png) 点击`header部分`和分割线不会关闭下拉,点击页面其它位置都会关闭下拉。 并且看云和`Bootstrap`的下拉都有这样的功能,那就是点开一个下拉展开,然后点击另一个下拉,那么前一个下拉关闭,这个点击展开新的下拉,也就是在多个下拉组件中点击,总会有一个出于展开,一个处于关闭,如果是监听在`document`的点击事件上,那么应该都关闭了啊,所以展开触发按钮应该阻止了事件冒泡,但是那又怎么关闭其它的呢,可见这其中有一定的设计,有时间好好研究下。 >[danger] 应该是目标元素上绑定展开事件(注意,目标元素触发时是不分冒泡还是捕获的),然后`document`上绑定的是捕获的关闭事件(关闭所有的层),这样`document`捕获事件先发生(屏幕上的任何点击事件,`document`的捕获事件都是第一个被触发的),就不能关闭还未触发的展开事件创建的层了,这样就能做到点击时一个处于关闭一个处于展开的效果了。 这个比较能体现浏览器中js的事件机制(捕获,冒泡)。 >[nice] 其实很简单,没那么复杂,d和下拉都是正常事件,下拉事件只需要控制当前的目标下拉与关闭就可以了,d是一个事件代理,关闭所有下拉,并取消活跃,但有一个情况例外,如果源为一个下拉的话,关闭就排除这个下拉。这样逻辑就正常了。(有赞商城底部菜单和看云就是这样的,这样的体验才是正确的。) ~~~ shopnc 的管理后台商品分类那里,更改分类名,用到了input的失去焦点事件的特性,不得不说这个天然的事件特性很巧妙。 shopnc nctouch-nav-menu 菜单,弹出时,使用一个透明的遮罩层,遮住整个页面,但它又在菜单的下面一层,这样就巧妙实现了,点其它地方(其实就是点到那个透明的遮罩层了)关闭菜单。 ~~~ * * * * * 这个地方有一个衔接,从这个衔接位置移出,2就不会隐藏了,不然从1移出2就立即消失,就导致鼠标不能移动到2上面了,衔接解决了这个问题。 ![](http://cdn.aipin100.cn/18-3-21/16508543.jpg) * * * * * ![](http://cdn.aipin100.cn/18-5-9/57628780.jpg) * * * * * 做一个 tips 插件, 鼠标可以移动上去的(从尖尖那里可以移上去) * * * * * ### TODO: return false;能阻止子元素的事件? ```javascript // html 结构:li.news-category-item>a // 选中的再次点击就会取消 $('.news-category-item').click(function(event) { if ($(this).hasClass('active')) { // window.location.href = "{:url('Article/news')}"; return false; } else { return true; } }); ``` 再次用`event.preventDefault()`和`event.stopPropagation();`实验了一下,原来不是阻止冒泡了,我说怎么会这么奇怪,怎么可能还能阻止子元素的事件呢,而是阻止默认事件了。 `return false === 阻止默认事件 + 阻止冒泡` 不过这个能阻止子元素的默认事件,这是由子元素而冒泡触发的事件啊,它不是事件源啊,也真是够奇怪的,有时间再来研究。 * * * * * last update:2018-6-26 17:48:43