绑定在被点击元素的事件是按照代码顺序发生,其他元素通过冒泡或者捕获“感知”的事件,按照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. Python基础(5)--字典

    字典由多个键及与其对应的值构成的对组成(把键值对成为项),每个键和它的值之间用冒号(:)隔开,项之间用逗号(,)隔开,而整个字典由一对大括号括起来.空字典由两个大括号组成:{} 本文地址:http:/ ...

  2. 【原/转】UITableview性能优化总结

    UITableView作为ios中使用最频繁的控件之一,其性能优化也是常常要面对的,尤其是当数据量偏大并且设备性能不足时.本文旨在总结tableview的几个性能优化tips,并且随着认识的深入,本文 ...

  3. Swift Standard Library: Documented and undocumented built-in functions in the Swift standard library – the complete list with all 74 functions

    Swift has 74 built-in functions but only seven of them are documented in the Swift book (“The Swift ...

  4. Win10 下使用 ionic 框架开发 android 应用之搭载开发环境

    转载请注明出处:http://www.cnblogs.com/titibili/p/5102035.html 谢谢~ 1.下载JDK并配置Java运行环境 http://www.oracle.com/ ...

  5. 关于Solr搜索标点与符号的中文分词你必须知道的(mmseg源码改造)

    关于Solr搜索标点与符号的中文分词你必须知道的(mmseg源码改造) 摘要:在中文搜索中的标点.符号往往也是有语义的,比如我们要搜索“C++”或是“C#”,我们不希望搜索出来的全是“C”吧?那样对程 ...

  6. SSH服务器拒绝了密码,xshell连不上虚拟机怎么办

    用Xshell链接虚拟机的时候,出现下面情况: 这是sshd的设置不允许root用户用密码远程登录 解决方案: 修改 vim /etc/ssh/sshd_config 找到# Authenticati ...

  7. R语言中数据框的横向合并与纵向合并

    #横向合并ID<-c(1,2,3,4)name<-c("Jim","Tony","Lisa","Tom")s ...

  8. 利用模板在RM里部署VM

    Refer to: https://www.azure.cn/documentation/articles/virtual-machines-windows-ps-template/ 过程中遇到的几个 ...

  9. linux—【绝对路径与相对路径】与【【文件基本操作】】(4)

    [绝对路径与相对路径] 绝对路径:我们在获得一个文件的时候,从根目录到二级到更多级目录都写全了, 终才找到这个文件,这种方式就是“绝对路径” 相对路径:目标文件与我本身文件的相对位置 当前目录:./  ...

  10. linux netstat 命令简解

    Netstat 简介: Netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告.常见参数-a (all)显示所有选项,默认不显示LISTEN相 ...