事件捕获、冒泡和事件委托
一、由来
为什么要有事件捕获和事件冒泡?
在主观意义上,我点击哪个元素,就只有哪个元素执行事件,这是合理的。
但是呢,在DOM树中,元素和元素的容器是一体的,就像人的四肢和人是一起的一样。所以触发了元素的事件,容器也势必是触发了事件的,就像你触摸了某个人的手臂,势必表示触摸了这个人一样。
为了表示这种“一体”的关系,于是有了事件捕获和事件冒泡。
二、概念
那么什么是捕获、什么是冒泡?
为了实现一体关系,有两种触发事件的顺序。一种是由上至下,一种是由下至上,它们分别是捕获和冒泡。
为了更好地理解,下面使用现实例子做类比。
冒泡的例子:
人的手臂被针刺到,会感觉到疼痛。
在这个例子中,针直接作用到人的手臂,手臂对被刺这个事件进行响应,发送神经信号。然后,大脑接收这个信号,产生痛觉。这样从低级神经到高级神经的传递顺序叫冒泡。也就是:
底层元素先对相应事件做出响应,然后再到上层元素做出响应,依次传递,这样的事件流叫冒泡。
然而,对于捕获,并不符合这种“一体”关系,反而像是为了实现这种一体关系而建立的机制。这里使用的是层层询问的方式。
一个按钮被点击了,根元素就知道了有元素被点击了,但是不知道直接被点击的是谁,但是自己肯定被点击。先触发自己的点击事件,询问下层元素,下层元素也先触发点击事件,再将询问向下传递,直到目标元素。
其中,不管捕获还是冒泡,(直接被作用元素)目标元素响应事件的过程叫目标过程。
三、图解
image.png四、执行顺序
现代浏览器都采用先捕获后冒泡的顺序,而在目标阶段,如果对同样的触发源,有多个响应事件的话,按事件添加的先后顺序执行。
<div id="parent">
parent
<div id="child">child</div>
</div>
var parent = document.getElementById('parent');
var child = document.getElementById('child');
// parent的捕获响应
parent.addEventListener('click', function(){
console.log('parent: catch click');
}, true);
// parent的冒泡响应
parent.addEventListener('click', function(){
console.log('parent: bubble click');
}, false);
// child的捕获响应
child.addEventListener('click', function(){
console.log('child: catch click');
}, true);
// child的冒泡响应
child.addEventListener('click', function(){
console.log('child: bubble click');
}, false);
// child的on绑定
child.onclick = function(){
console.log('child: on click');
};
结果:
parent: catch click
child: catch click
child: bubble click
child: on click
parent: bubble click
然后,经过将child的on绑定、捕获、冒泡响应事件调换顺序,都可以发现在目标过程并不符合先捕获后冒泡的约束,而是谁先绑定,谁先触发。当然,这也是因为它是一个单独的过程,独立于捕获和冒泡过程的原因,因为压根没关系嘛。
五、事件委托
其实事件委托就是,不在目标阶段对目标事件进行相应,改为放到捕获和冒泡阶段。而由于捕获和冒泡阶段发生在目标元素的上层元素(目标阶段的捕获和冒泡响应事件个人认为不应该划分到捕获和冒泡阶段比较好)
,就相当于把事件响应交给了上层元素,这就是事件委托,就这么简单。
<ul id="ul">
<li>a</li>
<li>b</li>
</ul>
var ul = document.getElementById('ul');
ul.addEventListner('click', function(){
console.log('li被点击');
}, false);
像上面这个例子,ul是被li撑开的,所以,点击ul势必会点击到li。这样,把li的响应操作给ul是可以考虑的,这就是事件委托。
提示:由于将多个li元素监听事件改为只有一个ul元素监听事件,一定程度上提高了性能。