DOM事件流之事件捕获以及事件冒泡

2,780 阅读4分钟

DOM事件流

事件流描述的是从页面中接收事件的顺序。

事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流

DOM事件流分为3个阶段:

1.捕获阶段

2.当前目标阶段

3.冒泡阶段

比如我们给一个div注册了点击事件,则DOM事件流图如下:

①->④是捕获阶段,④是当前目标阶段,④->⑦是冒泡阶段。

事件捕获

事件捕获是网景最早提出的一种事件流,由DOM最顶层节点开始,然后逐级向下传播到最具体的元素接收的过程。可以形象的比喻成将一个石头扔进水里,它会有一个下降到水底的过程,这个过程就是事件捕获阶段。

在事件捕获的概念下发生click事件的顺序应该是:

document -> html -> body -> 目标元素

事件捕获举例

<div class="outer">
    <div class="inner"></div>
</div>

此处省略css代码,绿色盒子为class="outer"的盒子,里面的橙色盒子为class="inner"的。

var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
        
// 若第三个参数为true,则表示在事件捕获阶段调用事件处理程序,默认值为false
outer.addEventListener('click',function(){
	console.log('我是捕获阶段的outer');
},true);

inner.addEventListener('click',function(){
	console.log('我是捕获阶段的inner');
},true);

document.addEventListener('click',function(){
    console.log('我是捕获阶段的document');
},true);

点击橙色区域,即点击inner盒子,控制台输出结果为:

显而易见,不管这几个事件注册顺序如何,事件执行都是从最顶层的元素(document)开始的

事件冒泡

事件冒泡是微软最早提出的,与事件捕获相反,是由最具体的元素接收,然后逐级向上传递到DOM最顶层节点的过程。可以形象的比喻成石头落入水中下降产生的泡泡从水底冒出水面的过程,这个就是事件冒泡阶段。

在事件冒泡的概念下发生click事件的顺序应该是:

目标元素 -> body -> html -> document

事件冒泡举例

<div class="outer">
    <div class="inner"></div>
</div>
var outer = document.querySelector('.outer');
var inner =  document.querySelector('.inner');
        
// 若第三个参数为false,则表示在事件冒泡阶段调用事件处理程序,默认值为false
outer.addEventListener('click',function(){
     console.log('我是冒泡阶段的outer');
},false);

inner.addEventListener('click',function(){
     console.log('我是冒泡阶段的inner');
},false);

document.addEventListener('click',function(){
     console.log('我是冒泡阶段的document');
},false);

还是上面那个盒子,点击橙色区域,即点击inner盒子,控制台输出结果为:

事件冒泡与事件捕获发生冲突

一般情况下若事件不标明是事件冒泡还是事件捕获的话,默认情况都是按事件冒泡来处理,

即事件监听器 element.addEventListener(event, function, useCapture) 的第三个参数为可选值,默认值为false。

若事件既有捕获也有冒泡的话,一般情况是先调用事件捕获阶段的事件,然后再调用事件冒泡阶段的事件

举个例子

var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

outer.addEventListener('click', function () {
     console.log('我是捕获阶段的outer');
}, true);

inner.addEventListener('click', function () {
     console.log('我是捕获阶段的inner');
}, true);

inner.addEventListener('click', function () {
     console.log('我是冒泡阶段的inner');
}, false);

outer.addEventListener('click', function () {
     console.log('我是冒泡阶段的outer');
}, false);

还是上面那个例子,点击橙色区域inner,则控制台输出结果为:

即执行顺序是从外到里,再从里到外

但其实目标事件发生捕获和冒泡的冲突时,事件的执行顺序跟事件的注册顺序有关!

假如把上面那串代码改为


var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

outer.addEventListener('click', function () {
     console.log('我是冒泡阶段的outer');
}, false);

inner.addEventListener('click', function () {
     console.log('我是冒泡阶段的inner');
}, false);

inner.addEventListener('click', function () {
    console.log('我是捕获阶段的inner');
}, true);

outer.addEventListener('click', function () {
    console.log('我是捕获阶段的outer');
}, true);

这次的运行结果就为:

因为点击的是inner橙色盒子,即inner为目标阶段,目标阶段的事件执行顺序的话是由事件的注册顺序决定的。由于上面的代码是先注册inner的冒泡再注册inner的捕获的,所以执行顺序为先输出冒泡的inner,再输出捕获的inner。

这个注册顺序只影响目标阶段的,不会对非目标阶段的事件进行影响。就是点击的只是inner的盒子,outer盒子的事件注册顺序不管怎么样,都是先执行捕获再执行冒泡的。

阻止事件冒泡

标准写法:利用事件对象里面的stopPropagation()方法

event.stopPropagation();

非标准写法:IE 6-8 利用事件对象cancelBubble属性

window.event.cancelBubble = true;

stopPropagation()方法兼容性问题

阻止事件冒泡的兼容性解决方案

var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

outer.addEventListener('click', function () {
     console.log('我是冒泡阶段的outer');
}, false);

inner.addEventListener('click', function (e) {
    console.log('我是冒泡阶段的inner');
	if(e && e.stopPropagation){
            e.stopPropagation();
	}else{
            window.event.cancelBubble = true;
	}
}, false);

输出结果:我是冒泡阶段的inner

由于这个阻止事件冒泡是声明在inner的,当目标阶段是在inner时才会阻止事件冒泡,若目标阶段发生在outer,即点击绿色区域,结果还是会向上冒泡。

有的事件是没有冒泡的,比如onblur、onfocus、onmouseenter、onmouseleave等,可以根据实际开发去使用,这里就不做过多解释。

本文是参考尚硅谷教学视频总结而来~~~