# 浅谈事件冒泡和事件捕获

​ 当界面产生一个事件时且此界面有元素嵌套时,事件分为了捕获阶段冒泡阶段

冒泡有一大优点,就是事件委托,而且经常用到,还能提高很大的性能。

在这里插入图片描述

# 1】事件捕获

事件捕获是指从外层元素往里层元素进行依次传递事件

# 2】事件冒泡

事件冒泡是指从里层元素往外层元素进行依次传递事件

注意这里传递的仅仅是事件并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件也不会有什么表现 但事件确实传递了。(这个事件就被认为是,子级元素被触发了自己绑定的事件,①如果父级元素本身也绑定了自己另外的事件,则此时也会触发该事件;②如果父级元素并没有绑定任何事件,此时虽然父级元素没有任何表现,但是事实上子级元素还是传递了事件,只是父级并没有东西去触发而已。)

# 我们来看看监听事件冒泡和事件捕获

微信小程序来举例:

<view class="view1" capture-bind:tap="captureView1" bindtap="bindView1">
    <view class="view2" capture-bind:tap="captureView2" bindtap="bindView2">
        <view class="view3" capture-bind:tap="captureView3" bindtap="bindView3"></view>
    </view>
</view>

在这里插入图片描述

captureView1(){
    console.log('捕获1')
}
bindView1(){
    console.log('冒泡1')
}
captureView2(){
    console.log('捕获2')
}
bindView2(){
    console.log('冒泡2')
}
captureView3(){
    console.log('捕获3')
}
bindView3(){
    console.log('冒泡3')
}

当我们点击绿色的view3时,所有的函数都会执行,此时是先从外层捕获进来再从里层冒泡出去

//控制台打印顺序
捕获1
捕获2
捕获3
冒泡3
冒泡2
冒泡1
# 而微信小程序中

bind:tap会一层层的传递

catch:tap会阻止事件的进一步传递

#vue中,我们使用事件修饰符来停止事件冒泡

@click.stop="test"

# 所以我们把刚才的代码改一改:

view2的事件冒泡改为catch-tap

<view class="view1" capture-bind:tap="captureView1" bindtap="bindView1">
    <view class="view2" capture-bind:tap="captureView2" catchtap="bindView2">
        <view class="view3" capture-bind:tap="captureView3" bindtap="bindView3"></view>
    </view>
</view>
//控制台打印顺序:此时发现并没有执行冒泡view1,被catch阻止了
捕获1
捕获2
捕获3
冒泡3
冒泡2

# 针对于原生JavaScript我们来看看事件冒泡

事件冒泡 :当一个元素接收到事件的时候会把他接收到的事件传给自己的父级,一直到window。(注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。)

# 栗子1:
var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");
   div2.onclick = function(){alert(2);};
   div1.onclick = function(){alert(1);};//父亲
//html代码

 <div id="div1">
    <div id="div2"></div>
 </div>

代码很简单,就是两个父子关系的div,然后分别加了点击事件,当我们在内层的div2里面点击的时候,会发现弹出了一次2,接着又弹出了1,这说明点击的时候,不仅div2的事件被触发了,它的父级的点击事件也触发了,这种现象就叫做冒泡。点击了div1,自己父级的点击事件也会被触发。

# 栗子2:

再看个栗子

var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2")  div1.onclick = function(){alert(2);}; // 父亲
//html代码
<div id="div1"><div id="div2"></div></div>

在这里插入图片描述

大家可以看一下效果图,相比于第一个例子,代码已经把儿子的点击事件去掉,只留下了父级的,测试的结果是当只点击了儿子,会弹出2,由此证明了当点击了儿子,父亲的点击事件被触发,执行了自己绑定的函数。由于一些人会以为显示出来儿子在父亲里面的时候,自然点了儿子相当于点了父亲,所以这个例子我故意把两个盒子绝对定位在了两个不同的位置,所以点击事件给页面显示出来的位置是没关系的,而是跟html代码中的位置有关系。

# 第三个栗子:

以上大概就是冒泡的形式了,而大多数时候,冒泡也带来了一些困扰,下面我们看一个栗子:

在这里插入图片描述

<script>
var div2 = document.getElementById("div2");
var div1 = document.getElementById("div1");

  div2.onclick = function(){   //红色面板事件
        div1.style.display = "block"; 
  };
 document.onclick = function(){ 
    div1.style.display = "none"; 
 }
</script>

这个时候我们测试发现,怎么点击红色面板,粉丝面板都不会显示了,为什么呢?就是冒泡的原因,我们来分析下代码,当点击div2的时候,他会触发父亲级别的点击事件,然后一层一层的往上传,所以document的点击事件自然也被触发了,然后执行了自己身上的绑定事件,让粉色面板消失。所以当你点击div2的时候首先,让粉丝面板显示,只是事件执行太快了,很快又执行了document的点击事件,让面板隐藏。 有兴趣的可以再两个事件之间加一个弹出,就可以测试。

那么这个时候我们肯定不希望有冒泡了,所以解决办法就是取消冒泡了:(后来补充)

# 取消事件冒泡有两种方式:

标准的W3C 方式:e.stopPropagation();这里的stopPropagation是标准的事件对象的一个方法,调用即可

非标准的IE方式:ev.cancelBubble=true;这里的cancelBubble是 IE事件对象的属性,设为true就可以了

通常我们会封装这样一个函数:

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

在最后再给大家推荐一篇很好的事件冒泡的文章:JS事件冒泡与捕获

Last Updated: 7/15/2020, 10:34:45 PM