先举一个例子,有一个父节点parent包含了一个子节点child,如下:
.outer{
border: 1px solid #ccc;
padding: 10px;
}
.inner{
border: 1px solid red;
padding: 10px;
}
<div id="parent" class='outer'>
parent
<div id="child" class='inner'>child</div>
</div>
const parentDom = document.getElementById('parent')
const childDom = document.getElementById('child')
事件流理解
DOM事件流包括三个阶段:
- 事件捕获:从最外层节点document逐级向下直到具体的事件对象,在该阶段不会接收事件。
- 处于目标阶段:事件在该阶段发生,并看成冒泡阶段的一部分。
- 事件冒泡:事件逐级向上传播回文档document。
如果对上述例子中的parent和child都定义click事件:
parentDom.addEventListener('click', (e)=>{
console.log('父节点点击')
})
childDom.addEventListener('click', (e)=>{
console.log('子节点点击')
})
如果点击父节点,则只会调用父节点中定义的事件,打印内容如下:
如果点击子节点,则事件捕获到具体的目标对象(子节点)后,调用对应的事件,然后进入冒泡阶段,父节点也会调用其click事件,因此打印内容如下。
阻止事件冒泡
在上述问题中,如果我们点击子节点时,只想触发子节点的事件,不让父节点的事件发生,则需要阻止事件冒泡。
childDom.addEventListener('click', (e)=>{
e.stopPropagation()
console.log('子节点点击')
})
点击子节点时,控制台打印内容:
如上所示,使用event对象的stopPropagation
方法可以阻止事件冒泡,这样就不会触发父节点的click事件。
还有一个阻止事件冒泡的方法是stopImmediatePropagation
,它与stopPropagation
的区别为:
- 如果对同一个元素绑定了多个event事件,则
stopImmediatePropagation
也会阻止该元素后面的event事件,而stopPropagation
不会阻止同一个元素的后面的event事件。
如下,对childDom定义了两个click事件的话,stopImmediatePropagation
会阻止后面的事件调用,不会打印出 '子节点点击第二次' ,而stopPropagation不会阻止,两个都会打印。
childDom.addEventListener('click', (e)=>{
// e.stopPropagation()
e.stopImmediatePropagation()
console.log('子节点点击第一次')
})
childDom.addEventListener('click', (e)=>{
console.log('子节点点击第二次')
})
阻止事件的默认行为
有些事件会有一些默认的行为,比如a标签点击时会导航到href指定的url,如果想阻止这些默认行为,可以使用event的preventDefalut属性。
<a href='' id='clickA'>点击</a>
const aDom = document.getElementById('clickA')
aDom.addEventListener('click', (e)=>{
e.preventDefault()
})
上述这个例子点击a标签时就不会出现页面刷一下的情况。
target与currentTarget区别
target
是事件的目标,即上述事件流中处于目标阶段的DOM节点。currentTarget
是绑定事件的DOM节点,其实可以想象为bindTarget。如下:
parentDom.addEventListener('click', (e)=>{
console.log(e.target,e.currentTarget)
console.log('父节点点击')
})
childDom.addEventListener('click', (e)=>{
console.log('子节点点击')
})
则点击子节点时,打印如下:
从上可知,target对象是child的DOM节点,而currentTarget是绑定事件的parent的DOM节点。