冒泡与捕获
事件冒泡与捕获是事件处理的两种机制,主要描述当在一个元素上有两个相同类型的事件处理器被激活会发生什么。
再点击子元素时,浏览器运行了两种不同的阶段:捕获阶段和冒泡阶段。捕获阶段的行为:
- 浏览器检查元素的最外层祖先
<html>
,是否在捕获阶段中注册了一个onclick
事件处理程序,如果是,则运行它; - 然后,它移动到
<html>
中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素;
而冒泡与捕获恰恰相反:
- 浏览器检查实际点击的元素是否在冒泡阶段中注册了一个
onclick
事件处理程序,如果是,则运行它; - 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达
<html>
元素;
而现代浏览器在默认情况下,所有事件处理程序都在冒泡阶段进行注册。因此上面代码在点击子元素时会先执行子元素绑定的事件,然后向上冒泡,触发父元素绑定的事件。
addEventListener
函数的第三个参数是个布尔值。含义:
- 当布尔值是
false
时(这也是默认值),表示向上冒泡触发事件; - 当布尔值是
true
时,表示向下捕获触发事件;
不能冒泡的事件
有些事件是不会冒泡的。比如:
blur
元素失去焦点时触发,focusout
事件也是失去焦点时触发,但可以冒泡;focus
元素获取焦点时触发;mouseenter
鼠标移动到元素上时会触发该事件,与之对应的是mouseover
事件,但会冒泡;mouseleave
鼠标离开元素时触发,与之对应的是mouseout
,但会冒泡;
事件冒泡可以让我们利用事件委托,尤其是处理大量子元素时,如果给每个子元素都绑定事件,这是不优雅的,可以将事件绑定到父元素上,并让子节点上发生的事件冒泡到父节点上,利用 e.target
属性可以获取到当前触发事件的子元素。
事件对象中的方法
stopPropagation()
阻止事件冒泡,当设置后,点击该元素时父元素绑定的事件就不会再触发;preventDefault()
阻止默认事件的发生;stopImmediatePropagation()
它用来阻止监听同一事件的其他事件监听器被调用以及阻止事件冒泡,比如给同一个div
元素绑定多个click
事件(使用addEventListener
方法可以注册多个),当在第二个事件函数中调用stopImmediatePropagation
方法时,点击div
元素时,后面注册的click
将不会被触发,而且还会阻止事件冒泡;
在 IE 浏览器中,使用
e.cancelBubble = true
也可以取消事件冒泡;使用e.returnValue = false
也能阻止默认事件的发生。
target 与 currentTarget 有什么不同?
target
属性指向的是事件目标,而 currentTarget
属性指向的是正在处理当前事件的对象,它总是指向事件绑定的元素。而 target
指向的可能不是定义时的事件目标。
例如:
div.addEventListener('click', (e) => {
console.log(e.target, e.currentTarget);
},false);
e.target
可能指向 div
元素,也可能指向它的子元素。而 e.currentTarget
总是指向 div
元素。
例题
下面代码,当输入框有焦点时,打印顺序是怎样的?
const ipt = document.querySelector('input');
ipt.addEventListener('click', () => {
console.log('click');
},false);
ipt.addEventListener('focus', () => {
console.log('focus');
}, false);
ipt.addEventListener('mousedown', () => {
console.log('mousedown');
}, false);
ipt.addEventListener('mouseenter', () => {
console.log('mouseenter');
}, false);
结果:
mouseenter // 鼠标移动到元素上时触发
mousedown // 鼠标按下时触发
focus // 输入框有焦点时触发
click // 点击事件最后触发