一次关于js事件出发机制反常的解决记录

汉王 笔记 2018年01月20日 收藏

起因:正常情况下我点击s2时是先弹出我是children,再弹出我是father,但是却出现了先弹出我是father,后弹出我是children的情况,这种情况是在和安卓app交互的h5页面中出现的,本地测试没有问题,但是在安卓打包的内嵌h5页面就出现了问题。简单化的代码先展示出来。

html代码如下

  1. <div id="father">s1
  2. <div id="children">s2
  3. </div>
  4. </div>

事件绑定如下

  1. $('#father').on('click',function (e) {
  2.    alert('我是father')
  3.  })
  4. $('#children').on('click',function (e) {
  5.    alert('我是children')
  6.    e.stopPropagation();
  7.  })

运行一下

借此问题,复习了一下js事件,先看一下几个定义

先来看事件注册

  1. // IE以外的其他浏览器
  2. // target :文档节点、document、window 或 XMLHttpRequest。
  3. // type :字符串,事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等。
  4. // listener :实现了 EventListener 接口或者是 JavaScript 中的函数。
  5. // useCapture :是否使用捕捉,一般用 false,事件触发时,会将一个 Event 对象传递给事件处理程序。
  6.  
  7. target.addEventListener(type,listener,useCapture);//添加
  8. target.removeEventListener(type,listener,useCapture);//删除
  1. // IE浏览器
  2. // target :文档节点、document、window 或 XMLHttpRequest。
  3. // type :字符串,事件名称,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等。
  4. // listener :实现了 EventListener 接口或者是 JavaScript 中的函数。
  5.  
  6. target.attachEvent(type, listener);//添加
  7. target.detachEvent(type, listener);// 移除

兼容写法

  1. 兼容后的方法
  2. var func = function(){};
  3. //例:addEvent(window,"load",func)
  4. function addEvent(elem, type, fn) {
  5. if (elem.attachEvent) {
  6. elem.attachEvent('on' + type, fn);
  7. return;
  8. }
  9. if (elem.addEventListener) {
  10. elem.addEventListener(type, fn, false);
  11. }
  12. }
  13.  
  14. //例:removeEvent(window,"load",func)
  15. function removeEvent(elem, type, fn) {
  16. if (elem.detachEvent) {
  17. elem.detachEvent('on' + type, fn);
  18. return;
  19. }
  20. if (elem.removeEventListener) {
  21. elem.removeEventListener(type, fn, false);
  22. }
  23. }

获取事件对象和事件源(触发事件的元素)

  1. function eventHandler(e){
  2. //获取事件对象
  3. e = e || window.event;//IE和Chrome下是window.event FF下是e
  4. //获取事件源
  5. var target = e.target || e.srcElement;//IE和Chrome下是srcElement FF下是target
  6. }

事件委托

  1. myTable.onclick = function () {
  2. e = e || window.event;
  3. var targetNode = e.target || e.srcElement;
  4. // 测试如果点击的是TR就触发
  5. if (targetNode.nodeName.toLowerCase() === 'tr') {
  6. alert('You clicked a table row!');
  7. }
  8. }

事件函数的解除绑定

和事件的绑定其实是相对应的,如果需要接触事件的绑定,运行对应的函数就可以了。如果是原生JS绑定则对应运行removeEventListener()和detachEvent()。

如果是jQuery的bind()和delegate()绑定,也是存在对应的解绑函数用以清除注册事件,比如unbind()和undelegate()。

看一个代码示例:

  1. var EventUtil = {
  2. //注册
  3. addHandler: function(element, type, handler){
  4. if (element.addEventListener){
  5. element.addEventListener(type, handler, false);
  6. } else if (element.attachEvent){
  7. element.attachEvent("on" + type, handler);
  8. } else {
  9. element["on" + type] = handler;
  10. }
  11. },
  12. //移除注册
  13. removeHandler: function(element, type, handler){
  14. if (element.removeEventListener){
  15. element.removeEventListener(type, handler, false);
  16. } else if (element.detachEvent){
  17. element.detachEvent("on" + type, handler);
  18. } else {
  19. element["on" + type] = null;
  20. }
  21. }
  22. };

再来看看事件流

几个概念

捕获阶段:事件对象通过目标的祖先从传播窗口到目标的父。这个阶段也被称为捕获阶段

目标阶段:本次活动对象到达事件对象的事件的目标。这个阶段也被称为目标阶段。如果事件类型指示事件不起泡,则在完成此阶段后,事件对象将停止。

冒泡阶段:事件对象通过目标的祖先中传播以相反的顺序,开始与目标的父和与所述结束窗口。这个阶段也被称为冒泡阶段

默认行为:事件通常由实现作为用户操作的结果分派,以响应任务的完成,或者在异步活动(例如网络请求)期间发信号通知进度。有些事件可以用来控制下一个实现可能采取的行为(或者撤销实现已经采取的行动)。这个类别中的事件被认为是可取消的,他们取消的行为被称为他们的默认行为

取消事件:可取消的事件对象可以与一个或多个“默认动作”相关联。要取消事件,请调用该preventDefault()方法。

一个图片

再上个小demo

  1. <ul>
  2. <li>点我试试</li>
  3. </ul>
  4. <div id="s1">s1
  5. <div id="s2">s2</div>
  6. </div>
  1. var ul = document.getElementsByTagName('ul')[0];
  2. var li = document.getElementsByTagName('li')[0]; element.addEventListener(event, function, useCapture)
  3. document.addEventListener('click',function(e){console.log('document clicked')},true);//第三个参数为true使用捕获,false为冒泡,false为默认
  4. ul.addEventListener('click',function(e){console.log('ul clicked')},true);
  5. li.addEventListener('click',function(e){console.log('li clicked')},true);
  6. //IE低版本兼容写法
  7. li.attachEvent('onclick',function(event){
  8. debugger
  9. console.log('li clicked');
  10. event.cancelBubble=true;
  11. });
  12.  
  13. s1.addEventListener('click',function () {
  14. console.log('s1 捕获方式')
  15. },true)
  16. s1.addEventListener('click',function () {
  17. console.log('s1 冒泡方式')
  18. },false)
  19. s2.addEventListener('click',function (e) {
  20. console.log('s2 捕获方式')
  21. // e.stopPropagation();
  22. },true)
  23. s2.addEventListener('click',function () {
  24. console.log('s2 冒泡方式')
  25. },false)

点击li时,打印 依次为

  1. ul clicked li clicked  

点击s1时,打印依次为

  1. s1 捕获方式 s1 冒泡方式

点击s2时,打印依次为

  1. s1 捕获方式 s2 捕获方式 s2 冒泡方式 s1 冒泡方式

处理事件冒泡和默认事件

1、e.preventDefault()

  1. var a = document.getElementById("testA");
  2. a.onclick =function(e){
  3. if(e.preventDefault){
  4. e.preventDefault();//
  5. }else{
  6. window.event.returnValue = false;//IE
  7.     //注意:这个地方是无法用return false代替的
  8.     //return false只能取消元素
  9. }
  10. }

2、return false  javascript的return false只会阻止默认行为,而是用jQuery的话则既阻止默认行为又防止对象冒泡。

  1. //原生js,只会阻止默认行为,不会停止冒泡
  2. var a = document.getElementById("testA");
  3. a.onclick = function(){
  4. return false;//当然 也阻止了事件本身
  5. };
  6. //既然return false 和 e.preventDefault()都是一样的效果,那它们有区别吗?当然有。
  7. //仅仅是在HTML事件属性 和 DOM0级事件处理方法中 才能通过返回 return false 的形式组织事件宿主的默认行为。
  1. 1 //jQuery,既阻止默认行为又停止冒泡
  2. 2 $("#testA").on('click',function(){
  3. 3 return false;//当然 也阻止了事件本身
  4. 4 });

总结使用方法

当需要停止冒泡行为时

  1. function stopBubble(e) {
  2. //如果提供了事件对象,则这是一个非IE浏览器
  3. if ( e && e.stopPropagation ){
  4. e.stopPropagation(); //因此它支持W3C的stopPropagation()方法
  5. }else{
  6. window.event.cancelBubble = true; //否则,我们需要使用IE的方式来取消事件冒泡
  7. }
  8. }

当需要阻止默认事件时

  1. function stopDefault( e ) {
  2. if ( e && e.preventDefault ){
  3. e.preventDefault(); //阻止默认浏览器动作(W3C)
  4. }else {
  5. window.event.returnValue = false; //IE中阻止函数器默认动作的方式
  6. }
  7. return false;
  8. }

最后的解决方法:

让我们回顾一下最初的问题,可能部分浏览器把事件的useCapture默认为true,导致点击子元素时父元素的事件先响应了,于是我的办法是在父元素的事件里进行判断

比如容器为#a,动态插入的元素为#b,在#a上监听click事件,判断event.target.id是不是等于b即可,如果.bclass这种,以此类推。

我们经常能遇到阻止冒泡,但是阻止捕获一般不会遇到,因为浏览器一般默认就给我们阻止了,只能说什么情况都有啊,万事还是得考虑周全。

作者:https://www.cnblogs.com/wuyuchao/p/8309290.html