javascript事件冒泡详解和捕获、阻止方法


Posted in Javascript onApril 12, 2014

一、事件的发生顺序

这个问题的起源非常简单,假设你在一个元素中又嵌套了另一个元素

-----------------------------------

| element1                        |

|   -------------------------     |

|   |element2               |     |

|   -------------------------     |

|                                 |

-----------------------------------

:并且两者都有一个onClick事件处理函数(event handler)。如果用户单击元素2,则元素1和元素2的单击事件都会被触发。但是哪一个事件先被触发?哪一个事件处理函数会被首先执行?换句话说,事件的发生顺序到底如何?

二、两种模型

不出所料,在那些“不堪回首”(浏览器大战)的日子里,Netscape和微软有两种截然不同的处理方法:

Netscape主张元素1的事件首先发生,这种事件发生顺序被称为捕获型
微软则保持元素2具有优先权,这种事件顺序被称为冒泡型
这两种事件顺序是截然相反的。Explorer浏览器只支持冒泡事件,Mozilla,Opera7和Konqueror两者都支持。而更古老的opera和iCab两者都不支持

三、捕获型事件

当你使用捕获型事件时

               | |

---------------| |-----------------

| element1     | |                |

|   -----------| |-----------     |

|   |element2  \ /          |     |

|   -------------------------     |

|        Event CAPTURING          |

-----------------------------------

:元素1的事件处理函数首先被触发,元素2的事件处理函数最后被触发

四、冒泡型事件

当你使用冒泡型事件时

               / \

---------------| |-----------------

| element1     | |                |

|   -----------| |-----------     |

|   |element2  | |          |     |

|   -------------------------     |

|        Event BUBBLING           |

-----------------------------------

:元素2 的处理函数首先被触发,元素1其次

五、W3C 模型

W3c明智的在这场争斗中选择了一个择中的方案。任何发生在w3c事件模型中的事件,首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段

                 | |  / \

-----------------| |--| |-----------------

| element1       | |  | |                |

|   -------------| |--| |-----------     |

|   |element2    \ /  | |          |     |

|   --------------------------------     |

|        W3C event model                 |

------------------------------------------

为一个web开发者,你可以选择是在捕获阶段还是冒泡阶段绑定事件处理函数,这是通过addEventListener()方法实现的,如果这个函数的最后一个参数是true,则在捕获阶段绑定函数,反之false,在冒泡阶段绑定函数。

假设你要做

element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)

如果用户单击元素2,则接下来会发生:

(事件在这里就像一个观光客,由外至内游览,逐渐接近被触发的主要元素,然后又反向离开)

1.单击事件首先进入捕获阶段开始(逐渐接近元素2的方向)。查看元素2的祖先元素中是否有在捕获阶段有onclick处理函数的
2.发现元素1有一个,于是doSomething2被执行
3.事件检查到目标自己(元素2),捕获阶段没有发现更多的处理函数了。事件开始进入冒泡阶段,想当然执行doSomething(),这个绑定于元素2冒泡阶段的函数。
4.事件向远离元素2的方向,查看是否有任何祖先元素在冒泡阶段绑定了一个处理函数。没有这样的情况,所以什么也没有发生
相反的情况是:

element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)

现在如果用户点击元素2会发生:

1.单击事件进入捕获阶段。查看元素2的祖先元素中是否有在捕获阶段有onclick处理函数的,结果一无所获
2.事件检查到目标自己。事件开始进入冒泡阶段,并且执行绑定于元素2冒泡阶段的函数。doSomething()
3.事件开始远离目标,检查元素2的祖先元素中是否有在冒泡阶段绑定了处理函数的
4.发现了一个,于是元素1的doSomething2()被执行

六、兼容性和传统模式

在支持w3c dom(文档对象模型) 的浏览器中,传统的事件绑定方法是

element1.onclick = doSomething2;

默认被视为在绑定于冒泡阶段

七、使用冒泡型事件

很少的开发人员会有意识的去使用冒泡型事件或者捕获型事件。在他们今天制作的网页中,没有必要让一个事件因为冒泡而被好几个函数处理。但是有时用户通常会很疑惑,因为在他们只点击了一次鼠标之后出现了许多种情况(多个函数被执行,因为冒泡)。而大多数情况下你还是希望你的处理函数相互独立的。当用户点击了某一个元素,发生什么,点击另一个元素,又对应发生些什么,相互独立,而不因为冒泡连锁。

八、一直在发生

首先你要明白的是事件捕获或者冒泡一直在发生。如果你给整个页面文档的定义一个通用onclick处理函数

document.onclick = doSomething;

if (document.captureEvents) document.captureEvents(Event.CLICK);

在页面上单击任何元素的单击事件,最终会冒泡至页面最高文档层,因此触发那个通用的处理函数,除非之前一个处理函数明确的指出终止冒泡,这样才冒泡才不会传播到整个文档层面

对上面代码第二句的补充:

>>>先说IE
object.setCapture() 当一个object的被 setCapture 后,他的方法将会被继承到整个文档进行捕获。
当不需要把方法继承到整个文档捕获时,要用 object.releaseCapture()
>>>others
Mozilla 也有类似的功能,方法稍微不同
window.captureEvents(Event.eventType)
window.releaseEvents(Event.eventType)
>>>example

//如果只有下面这句话,那只有点击obj是才会触发click    obj.onclick = function(){alert("something")}

//若加上下面这句话,则方法会被继承到document(或者window,不同浏览器不同)来捕获

obj.captureEvents(Event.click); //FF

obj.setCapture()  //IE

九、用法

因为任何事件传播终止于页面文档(这个最高层),这使默认的事件处理函数变得可能,假设你有这样一个页面

------------------------------------

| document                         |

|   ---------------  ------------  |

|   | element1    |  | element2 |  |

|   ---------------  ------------  |

|                                  |

------------------------------------

element1.onclick = doSomething;

element2.onclick = doSomething;

document.onclick = defaultFunction;

 

现在如果用户单击元素1或者元素2,doSomething()将被执行。如果你愿意的话,如果你不想让事件冒泡至执行defaultFunction(),你可以在这里阻止事件冒泡向上传播,。但是如果用户点击页面上的其他部位,defaultFunction()还是会被执行。这样的效果或许有时能用的上。

设置页面­——使处理函数有范围较大的触发面积,在“拖拽效果”脚本中是必须的。一般来说在某一个元素层上发生 mousedown事件意味着选择了这个元素,并且使它能够响应mousemove事件。虽然mousedown通常绑定于这个元素层上以避免浏览器bug,但是其他两者的事件函数的范围必须是整个页面(?)

记住浏览器学的第一法则(First Law of Browserology)是:一切皆有可能(anything can happen),并且是在你起码有点准备的时候。所以有可能发生的是,用户拖拽时,大幅度在页面上移动他的鼠标,脚本却不能在大幅度中做出反应,以至于鼠标也就不再停留在元素层上了

1.如果onmouseover处理函数绑定在元素层上,这个元素层不会再对鼠标的移动有任何反应,这会让用户觉得奇怪
2.如果onmouseup处理函数绑定在元素层上,事件也不能被触发,后果是,用户想放下这个元素层后,元素层持续对鼠标移动做出反应。这会引起(用户)更多的迷惑(?)

所以在这个例子中,事件冒泡非常的有用,因为将你的处理函数放在页面层能保证他们一直能被执行

十、把它给关了(阻止事件冒泡)

但是一般情况下,你会想关了所有的冒泡和捕获以保证函数之间不会打扰到对方。除此之外,如果你的文档结构相当的复杂(许多table之间相互嵌套或者诸如此类),你也会为了节省系统资源,而关闭冒泡。此时浏览器不得不检查目标元素的每一个祖先,看是否它有一个处理函数。即使一个都没有找到,刚刚的搜索同样花费不少时间

在微软的模型中,你必须设置事件的cancelBubble的属性为true

window.event.cancelBubble = true

在w3c模型中你必须调用事件的stopPropagation()方法
e.stopPropagation()

这会阻止所有冒泡向外传播。而作为跨浏览器解决方案应该这么作:
function doSomething(e)
{


  if (!e) var e = window.event;
         e.cancelBubble = true;
         if (e.stopPropagation) e.stopPropagation();
}

在支持cancelBubble属性的浏览器中设置cancelBubble无伤大雅。浏览器会耸一耸肩然后创造一个这个属性。当然这也并不能真正的取消冒泡,但至少能保证这条命令是安全正确的

十一、currentTarget

像我们之前看到的一样,一个事件用target或者是srcElement属性用来表示事件究竟发生在哪个目标元素上(即用户最初点击的元素)。在我们的例子中是元素2,因为我们单击了它。

非常重要的是,要明白在捕获或者冒泡阶段的目标元素是不变的,它始终与元素2相关联。

但是假设我们绑定了以下函数

element1.onclick = doSomething;
element2.onclick = doSomething;

如果用户单击元素2, doSomething()会被执行两次。但是你怎么知道哪个html元素正在响应这个事件?target/srcElement也没有给出线索,但人们总是更倾向于元素2,因为它是引起事件的原因(因为用户点击的是它)。
为了解决这个问题,w3c 增加了currentTarget这个属性,它就指向正在处理事件的元素:这恰是我们需要的。很不幸的是微软模型中并没有相似的属性
你也可以使用”this”关键字。在上面的例子中,它相当于正在处理事件的html元素,就像currentTarget。

十二、微软模型的问题

但是当你使用微软事件绑定模型时,this关键字并不相当于HTML元素。联想缺少类似currentTarget属性的微软模型(?)——按上面的代码操作的话,你这么做便意味着:

element1.attachEvent('onclick',doSomething)
element2.attachEvent('onclick',doSomething)

你无法确切知道哪一个HTML元素正在负责处理事件,这是微软事件绑定模型最严重的问题,对我来说,这也是我从不使用它的原因,哪怕是在开发仅供Windows下的IE的应用程序

我希望能够尽快增加currentTarget类似的属性——或者遵循标准?web开发者们需要这些信息

后记:

因为没有实战过javascript,所以这篇文章有些地方我并不是很理解,只能硬生的翻译出来,比如谈拖拽效果的那一段,如果大家有什么补充和疑问可以留言给我,谢谢支持啦!

 PS:这里再为大家推荐一款关于JS事件的在线查询工具,归纳总结了JS常用的事件类型与函数功能:

javascript事件与功能说明大全:

Javascript 相关文章推荐
提高 DHTML 页面性能
Dec 25 Javascript
js获取div高度的代码
Aug 09 Javascript
JavaScript入门教程(1) 什么是JS
Jan 31 Javascript
window.location 对象所包含的属性
Oct 10 Javascript
前端弹出对话框 js实现ajax交互
Sep 09 Javascript
JavaScript每天必学之数组和对象部分
Sep 17 Javascript
微信小程序 加载 app-service.js 错误解决方法
Oct 12 Javascript
浅谈webpack下的AOP式无侵入注入
Nov 12 Javascript
原生js实现Flappy Bird小游戏
Dec 24 Javascript
JS co 函数库的含义和用法实例总结
Apr 08 Javascript
Node.js fs模块原理及常见用途
Oct 22 Javascript
jQuery ajax - getScript() 方法和getJSON方法
May 14 jQuery
一个非常全面的javascript URL解析函数和分段URL解析方法
Apr 12 #Javascript
用jquery写的菜单从左往右滑动出现
Apr 11 #Javascript
瀑布流布局代码一例
Apr 11 #Javascript
javascript读写json示例
Apr 11 #Javascript
javascript如何判断输入的url是否正确
Apr 11 #Javascript
javascript判断office版本示例
Apr 11 #Javascript
JS、jquery实现几分钟前、几小时前、几天前等时间差显示效果的代码实例分享
Apr 11 #Javascript
You might like
求PHP数组最大值,最小值的代码
2011/10/31 PHP
使用php实现下载生成某链接快捷方式的解决方法
2013/05/07 PHP
PHP删除数组中空值的方法介绍
2014/04/14 PHP
php截取指定2个字符之间字符串的方法
2015/04/15 PHP
PHP加密3DES报错 Call to undefined function: mcrypt_module_open() 如何解决
2016/04/17 PHP
PHP 中常量的知识整理
2017/04/14 PHP
代码精简的可以实现元素圆角的js函数
2007/07/21 Javascript
jQuery asp.net 用json格式返回自定义对象
2010/04/07 Javascript
struts2+jquery+json实现异步加载数据(自写)
2013/06/24 Javascript
JS验证日期的格式YYYY-mm-dd 具体实现
2013/06/29 Javascript
js格式化时间和js格式化时间戳示例
2014/02/10 Javascript
[将免费进行到底]在Amazon的一年免费服务器上安装Node.JS, NPM和OurJS博客
2014/08/18 Javascript
jQuery中next()方法用法实例
2015/01/07 Javascript
jQuery Ajax调用WCF服务详细教程
2015/03/31 Javascript
基于javascript实现图片预加载
2016/01/05 Javascript
ES6的新特性概览
2016/03/10 Javascript
jQuery的ajax下载blob文件
2016/07/21 Javascript
浅谈express 中间件机制及实现原理
2017/08/31 Javascript
详解vue+css3做交互特效的方法
2017/11/20 Javascript
基于js文件加载优化(详解)
2018/01/03 Javascript
详解vantUI框架在vue项目中的应用踩坑
2018/12/06 Javascript
Vue 前端实现登陆拦截及axios 拦截器的使用
2019/07/17 Javascript
vue 二维码长按保存和复制内容操作
2020/09/22 Javascript
JavaScript实现雪花飘落效果
2020/12/27 Javascript
[01:21]DOTA2周边文化主题展 神秘商店火热开售
2017/07/30 DOTA
对python中的高效迭代器函数详解
2018/10/18 Python
Flask核心机制之上下文源码剖析
2018/12/25 Python
python爬虫租房信息在地图上显示的方法
2019/05/13 Python
pyinstaller参数介绍以及总结详解
2019/07/12 Python
在python 中split()使用多符号分割的例子
2019/07/15 Python
英国剑桥包中文官网:The Cambridge Satchel Company中国
2018/11/06 全球购物
美国精品地毯网站:Boutique Rugs
2020/03/04 全球购物
好家长事迹材料
2014/01/23 职场文书
上诉状格式
2015/05/23 职场文书
《狮子和鹿》教学反思
2016/02/16 职场文书
使用Djongo模块在Django中使用MongoDB数据库
2021/06/20 Python