绑定在被点击元素的事件是按照代码顺序发生,其他元素通过冒泡或者捕获“感知”的事件,按照W3C的标准,先发生捕获事件,后发生冒泡事件。所有事件的顺序是:其他元素捕获阶段事件 -> 本元素代码顺序事件 -> 其他元素冒泡阶段事件 。

一个DOM元素绑定两个事件,一个冒泡,一个捕获,则事件会执行多少次,执行顺序如何。

首先来了解一下冒泡和捕获是怎么回事:

1.冒泡

冒泡是从下向上,DOM元素绑定的事件被触发时,此时该元素为目标元素,目标元素执行后,它的的祖元素绑定的事件会向上顺序执行。如下代码所示,四个嵌套的div:

addEventListener函数的第三个参数设置为false说明不为捕获事件,即为冒泡事件。

<div id='one'>
<div id='two'>
<div id='three'>
<div id='four'>
</div>
</div>
</div>
</div> <script type='text/javascript'>
var one=document.getElementById('one');
var two=document.getElementById('two');
var three=document.getElementById('three');
var four=document.getElementById('four');
one.addEventListener('click',function(){
alert('one');
},false);
two.addEventListener('click',function(){
alert('two');
},false);
three.addEventListener('click',function(){
alert('three');
},false);
four.addEventListener('click',function(){
alert('four');
},false);
</script>

代码的执行顺序是:

点击one元素,输出one;

点击two元素,输出two one;

点击three元素,输出 three two one;

点击four元素,输出 four three two one;

2.捕获

捕获则和冒泡相反,目标元素被触发后,会从目标元素的最顶层的祖先元素事件往下执行到目标元素为止。

将上面的代码第三个参数均改为true,则执行结果如下:

点击one,输出one;

点击two,输出one two;

点击three,输出one two three;

点击four,输出one two three four;

很明显执行顺序是不同的。

3.当一个元素绑定两个事件,一个冒泡,一个捕获

首先,无论是冒泡事件还是捕获事件,元素都会先执行捕获阶段。

从上往下,如有捕获事件,则执行;一直向下到目标元素后,从目标元素开始向上执行冒泡元素,即第三个参数为true表示捕获阶段调用事件处理程序,如果是false则是冒泡阶段调用事件处理程序。(在向上执行过程中,已经执行过的捕获事件不再执行,只执行冒泡事件。)

如下代码:

one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two');
},false);
three.addEventListener('click',function(){
alert('three');
},true);
four.addEventListener('click',function(){
alert('four');
},false);

此时点击four元素,four元素为目标元素,one为根元素祖先,从one开始向下判断执行。

one为捕获事件,输出one;

two为冒泡事件,忽略;

three为捕获时间,输出three;

four为目标元素,开始向上冒泡执行,输出four;(从此处分为两部分理解较容易。)

three为捕获已执行,忽略;

two为冒泡事件,输出two;

one为捕获已执行,忽略。

最终执行结果为:one three four two

(在这里可能会有疑问,目标元素是什么事件有区别吗?我的测试结果是没有区别的,无论目标元素是捕获还是冒泡,在w3c下都是先从根元素执行捕获到目标元素,再从目标元素向上执行。)

例如,three作为目标元素,执行结果为:one three two(因为two是冒泡事件,在向下执行时没有执行到)。

执行次数:绑定了几个事件便执行几次。

如下代码,two元素绑定了两个不同事件,点击two都会执行这两个事件。而执行顺序有所差异

one.addEventListener('click',function(){
alert('one');
},true);
two.addEventListener('click',function(){
alert('two,bubble');
},false);
two.addEventListener('click',function(){
alert('two,capture');
},true);
three.addEventListener('click',function(){
alert('three,bubble');
},true);
four.addEventListener('click',function(){
alert('four');
},true);

1、如果two为目标元素,目标元素的事情按顺序执行,而其他元素根据W3C的标准执行,即先捕获后冒泡。

点击two执行结果:one(因为是two的父元素支持捕获事件所以先执行)  two,bubble  two,capture(顺序执行,注意逗号不是间隔,是输出内容。)

2、如果目标元素不是two,则two的两个事件按先捕获后冒泡触发执行,也就是跟前面讨论的执行过程是一样的,只不过两个事件都绑定在同一个DOM元素上。

点击three执行结果:one two,capture three,bubble two,bubble

再来一个例子:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style type="text/css">
#p { width: 300px; height: 300px; padding: 10px; border: 1px solid black; }
#c { width: 200px; height: 200px; border: 1px solid red; }
#sub { width: 100px; height: 100px; border: 1px solid red; }
</style>
</head>
<body>
<div id="p">
parent
<div id="c">
child
</div>
</div>
<script type="text/javascript">
window.alert = function (msg) {
console.log(msg);
};
var p = document.getElementById('p'),
c = document.getElementById('c');
p.addEventListener('click', function (e) {
alert('父节点冒泡')
}, false); c.addEventListener('click', function (e) {
alert('子节点捕获')
}, true);
c.addEventListener('click', function (e) {
alert('子节点冒泡')
}, false);
p.addEventListener('click', function (e) {
alert('父节点捕获')
}, true);
</script>
</body>
</html>

点击子div:

p.addEventListener('click', function (e) {
alert('父节点冒泡')
}, false); c.addEventListener('click', function (e) {
alert('子节点冒泡')
}, false);
c.addEventListener('click', function (e) {
alert('子节点捕获')
}, true); p.addEventListener('click', function (e) {
alert('父节点捕获')
}, true);

所有事件的顺序是:其他元素捕获阶段事件 -> 本元素代码顺序事件 -> 其他元素冒泡阶段事件 。

还是那句话,没在实际应用中踩过坑,可能你真的不算掌握了这个知识点。那么,事件捕获有什么常见的坑呢?

一、事件捕获

1.坑一:新插入的子元素没有绑定点击事件

可能不少同学在初学js的时候遇到过这样一个坑。一个Ul元素中出事的状态有4个li元素,我们可以循环给li元素添加click事件,执行我们想要的动作。这个例子的坑就在于,新添加的li元素不会有我们绑定的click事件。

<ul class="container">

    <!-- 先循环给原有的4个li绑定click事件 -->
<li class="item"></li>
<li class="item"></li>
<li class="item"></li>
<li class="item"></li> <!-- 这是新添加的li元素,但是该元素没绑定click事件 -->
<li class="item new"></li>
</ul>

是的,就是这么坑,为了解决这个问题,我们就要利用事件捕获的原理。

$('ul.container').click(function(event) {
var target = event.target;
if (target.className == 'item') {
// dosomething
}
})

在上面的解决方案中,我并不是直接给li绑定事件,而是给所有li的父级ul绑定事件。根据事件捕获的原理,事件会自上而下传递给li,我们只需要通过一些简单的条件判断来确定我们的目标元素即可,如上例中的判断li的className。

这个方式就是大名鼎鼎的事件委托。(可以看我的另一篇博客js中的事件委托)

事件委托是一个很重要而且在实际中会常常用到的知识点。

2. 坑二: 如果目标元素有子元素,那么怎么办?

当我们在使用className判断目标元素时,会遇到这样的情况。

...
<li class="item">
<div class="title">xxx</title>
<p class="desc">xxxxxxs</p>
</li>
...

当我们试图使用事件委托希望给所有的li添加元素时,在利用className判断的过程中发现,目标event.target元素居然是li.item的子元素,这个时候就没办法准确的将事件绑定到li上了,这个时候我们应该怎么办?

这种情况,我们要做的,就是阻止事件捕获的传递,那么,要如何阻止呢?

我知道的一种方式,利用css,给li所有子元素添加如下css属性即可

li.item >  * {
pointer-events: none;
}

按道理来说,应该是有js方式的,可是查了很多文章都没有提及,因此就暂时这样吧,到时候遇到了在补充。

在jquery中,已经帮助我们实现了事件委托,并且帮我们解决掉了这些坑。我们只需要按照一定语法使用即可,而我们不用再自己去进行条件判断,比如我们要给所有的li.item元素绑定事件,写法如下

// on中的第二参数就是我们的目标元素的选择器
$('ul.container').on('click', 'li.item', function(event) {
// dosomething
})

二、事件冒泡

说完了事件捕获,然后来说说事件冒泡这个坑。所谓事件冒泡,就是让DOM树最底层的目标元素最先接收到事件,然后往上传递,这是一个自下而上的过程。

1.弹窗关闭时阻止冒泡

我们常常会遇到一种弹窗样式,弹窗出来时,内容在中间,然后会有一层半透明的遮罩将页面内容与弹窗区分开。弹窗内容会有一些按钮绑定点击事件,比如确认与取消。而在半透明遮罩上,可能也会绑定一个点击事件,当点击时,将弹窗隐藏。如果我由于经验不足,将该遮罩层设置成了弹窗按钮的父级,那么就会遇到事件冒泡带来的麻烦。

也就是说,在如下例子中 container是全屏遮罩,button是弹窗里面的点击按钮。他们都同时绑定了click事件,执行不同的动作。但是在实际执行的时候,当我点击了button,那么button和container的click事件都会执行,自下而上按顺序执行

<div className="container">
<div className="button">click</div>
</div>

好吧解决问题的方法很简单,就是阻止冒泡事件。

$xxx.click(function(e) {
e.stopPropagation(); // ie
e.cancelBubble = true;
})

2.使用toggle时阻止冒泡

 $(function(){  
        //toggle div  
        $("#gga").click(function(event){  
            event.stopPropagation(); //阻止冒泡事件  
            $(".toggleoption").slideToggle("fast");  
        });  
  
        //slideup div  
        $(document).click(function(event)  {   
            var eo  =  $(event.target);                if($(".toggleoption").is(":visible") && eo.attr("class") != "toggleoption" && !eo.parent(".toggleoption").length) {  
                $('.toggleoption').slideUp("fast");   
            }  
        });   
    }) 

理解了整个事件流,我们可以感受事件在DOM中的传递过程与方向,并且利用他来解决我们的问题和各种坑,虽然是一个简单的知识点,但是却非常容易被忽略,因此建议大家找机会将它掌握牢固。

而还有一个坑,可能大家在实际中会很少遇到,这个坑就是,某些事件类型天生就不支持事件冒泡!

blur: 在元素失去焦点时触发,该事件不支持冒泡
focus: 在元素获得焦点时触发,该事件不支持冒泡
mouseenter: 当鼠标移入元素时触发,该事件不支持冒泡
mouseleave: 当鼠标移出元素时触发,该事件不支持冒泡
... ...

以及IE6 IE7 IE8中 的 change、select、submit、reset 事件事实上都没有参照规范定义产生事件冒泡。

当你在需要的冒泡的时候,绑定了这些事件,而你恰好不知道居然还有事件天生就不支持冒泡的,那么你可能就悲剧了。所以这个点只要在脑袋里面有个印象就好了,我只记得我曾经遇到过这样一个坑,但暂时想不起来应用场景了 - -!

一个DOM元素绑定多个事件时,先执行冒泡还是捕获的更多相关文章

  1. 浏览器中开发人员工具快速找到dom元素绑定那些JS事件

    在web开发过程中难免会遇到让程序员去修改一些js代码东西,例如js的ajax和php等语言的交互等,在这其中你不得不了解点js的事件触发,且随着js的盛行各种插件的事件让程序员眼花缭乱,所以借助一个 ...

  2. 随笔一个dom节点绑定事件

    以下利用jquery说明: js中,给一个dom节点绑定事件再平常不过了.这里说下,如果dom经常发生变化的话,给这个dom绑定事件的情况. 比如代码如下: li的节点,绑定了事件:点击会打出来里头的 ...

  3. Js为Dom元素绑定事件须知

    为异步加载的Dom 元素绑定事件必须在加载完成之后绑定: $('body').load('LearnClickBinding.ashx');$('a').click(function () { ale ...

  4. 一个DOM元素同时拥有多个类名时的样式产生冲突时 属性取决于css样式表中后读取到的属性

    如果一个DOM元素包含多个类名,其中的两个类名的属性产生冲突,并不是根据htnl中类名的顺序来决定DOM元素的属性, 而是根据css样式中的顺序来决定DOM元素的属性,它取决于css样式表中后读取到的 ...

  5. 父元素a标签的href默认行为以及子元素绑定的click事件的响应之间存在影响

    原文地址 背景 开发过程中遇到问题,简单写个demo 运行环境为Chrome 68 描述一下这个问题,当a标签内部存在嵌套时, 父元素a标签的href默认行为以及子元素绑定的click事件的响应之间存 ...

  6. jq给同一元素绑定多个事件

    $(".aa").on("click",function(){ alert(1) }).on("mousemove",function(){ ...

  7. vue中一个dom元素可以绑定多个事件?

    其实这个问题有多个解决方法的  这里提出两点 第一种 第二种 现在dom上绑定一个 然后在你的methods中直接调用 如果要传参数  这时候千万别忘记 原创 如需转载注明出处 谢谢

  8. Chrome插件Visual Event查看Dom元素绑定事件的利器

    找这工具找了好久,统一找着了,开发人员不可多得的好东东,收藏做一下分享. 用Chrome插件Visual Event查看Dom绑定的事件 Visual Event简介 Visual Event是一个开 ...

  9. dom元素操作(动态事件绑定)

    遇到的问题:动态生成的内容,事件绑定会出错. 例子:http://snowinmay.net/jqm/dom-learn2.html 1.绑定事件对动态生成的新元素无效. 问题描述:bind事件绑定后 ...

随机推荐

  1. Sharepoint学习笔记—习题系列--70-573习题解析 -(Q25-Q27)

    Question25You develop a new publishing page layout named MyPage.aspx for a SharePoint site.You creat ...

  2. 多线程在iOS开发中的应用

    多线程基本概念 01 进程 进程是指在系统中正在运行的一个应用程序.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内. 02 线程 2-1 基本概念 1个进程要想执行任务,必须得有线程 ...

  3. 关于第三方IOS的checkBox框架的使用

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  4. Http协议中 常用的参数应用

    1 请求来自哪一个页面 request.getHeader("referer"); 在购买页,通过a标签进入AddressAction中,地址保存后,需要跳到原先的页面. 另外,另 ...

  5. 网络编程1--毕向东java基础教程视频学习笔记

    目录: 01 网络编程概述1 02 网络编程概述2 03网络编程 网络模型 04网络编程 IP地址 05网络编程 TCP和UDP 06网络编程 Socket 07网络编程 UDP发送端 01 网络编程 ...

  6. Effective Java 70 Document thread safety

    Principle The presence of the synchronized modifier in a method declaration is an implementation det ...

  7. 问题解决——VS2010 将生成的文件复制到指定位置

    我是从VC6直接过渡到VS2010的,VS2008没怎么用过.用VS2010的时候,每次生成dll后,手工把dll.lib..h文件复制到指定文件夹太麻烦了,所以着手写了这个. =========== ...

  8. Count and Say

    Count and Say https://leetcode.com/problems/count-and-say/ The count-and-say sequence is the sequenc ...

  9. linux文件压缩与打包

    在linux中常见的压缩命令 首先,在linux中压缩文件的扩展名大多是 *.gz gzip程序压缩的文件 *.bz2 bzip2程序压缩的文件 *.tar tar程序打包的数据,并没有压缩过 *.t ...

  10. Java基础の乱弹琴二:break关键字

    Java中的break一般用于 跳出一个switch或者循环. 跳出switch基本不用赘述. break跳出循环一般是跳出当前一层循环. 如若需要跳出多层循环可以在break后加标签,然后把标签标注 ...