事件冒泡及捕获
概念
eventflow在MDN关于 事件流的文档上的一张图片,清晰地展示了三个event phase:捕获阶段,目标阶段,冒泡阶段。如果event object不支持某个event phase,该阶段就会被跳过。如果事件对象的传播被禁止,所有的event phase都会被跳过。
举例
借用MDN上的一个例子,来解释事件冒泡:
<button>Display video</button>
<div class="hidden">
<video>
<source src="rabbit320.mp4" type="video/mp4">
<source src="rabbit320.webm" type="video/webm">
<p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
</video>
</div>
当‘’button‘’元素按钮被单击时,将显示视频,它是通过将改变<div>的class属性值从hidden变为showing(这里包含两个类的CSS代码)
btn.onclick = function() {
videoBox.setAttribute('class', 'showing');
}
然后我们再添加几个onclick事件处理器,第一个添加在<div>元素上,第二个添加在<video>元素上。这个想法是当视频(<video>)外 <div>元素内这块区域被单击时,这个视频盒子应该再次隐藏;当单击视频(<video>)本身,这个视频将开始播放。
videoBox.onclick = function() {
videoBox.setAttribute('class', 'hidden');
};
video.onclick = function() {
video.play();
};
当您点击video开始播放的视频时,它会在同一时间导致<div>也被隐藏。 这是因为video在<div>之内 - video是<div>的一个子元素 - 所以点击video实际上是同时也运行<div>上的事件处理程序。
事件冒泡线路
在现代浏览器中,默认情况下,所有事件处理程序都在冒泡阶段进行注册。
因此,在我们当前的示例中,当您单击视频时,这个单击事件从 <video>元素向外冒泡直到<html>元素。沿着这个事件冒泡线路:
- 它发现了video.onclick...事件处理器并且运行它,因此这个视频<video>第一次开始播放。
- 接着它发现了(往外冒泡找到的) videoBox.onclick...事件处理器并且运行它,因此这个视频<video>也隐藏起来了。
用stopPropagation()修复问题
标准事件对象具有可用的名为 stopPropagation()
的函数, 当在事件对象上调用该函数时,它只会让当前事件处理程序运行,但事件不会在冒泡链上进一步扩大,因此将不会有更多事件处理器被运行(不会向上冒泡)。所以,我们可以通过改变前面代码块中的第二个处理函数来解决当前的问题:
video.onclick = function(e) {
e.stopPropagation();
video.play();
};
W3C规范
语法:
element.addEventListener(event, function, useCapture)
event:(必需)绑定的事件名称
function:(必需)触发事件后要执行的函数;
useCapture:(可选)默认是false,默认在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。
事件委托
事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果。
优点
1.减少内存消耗
试想一下,若果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件;
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li
如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能;
因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul
上,然后在执行事件的时候再去匹配判断目标元素;
所以事件委托可以减少大量的内存消耗,节约效率。
2.动态绑定事件
比如上述的例子中列表项就几个,我们给每个列表项都绑定了事件;
在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;
如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;
所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。
jQuery 中的事件委托
$.on:
基本用法: $('.parent').on('click', 'a', function () { console.log('click event on tag a'); })
,它是 .parent 元素之下的 a 元素的事件代理到 $('.parent') 之上,只要在这个元素上有点击事件,就会自动寻找到 .parent 元素下的 a 元素,然后响应事件;
局限性
当然,事件委托也是有一定局限性的;
比如 focus
、blur
之类的事件本身没有事件冒泡机制,所以无法委托;
mousemove
、mouseout
这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的;
总结
- 适合用事件委托的事件:
click
,mousedown
,mouseup
,keydown
,keyup
,keypress
。 - 所有的鼠标事件中,只有
mouseenter
和mouseleave
不冒泡,其他鼠标事件都是冒泡的。
网友评论