事件传递
当我们同时为父元素和子元素绑定相同事件时可能会发现子元素事件被触发时,父元素同时也会被触发。
如下範例,div
和 button
都绑定了 click
事件,为什么我们点选按钮时会触发 div
的 click
事件呢?
<div style="background-color: lightgoldenrodyellow;"> <button id="button">点选</button></div><script> var button = document.getElementById('button'); button.addEventListener('click', function(){ console.log('点按钮'); }); var div = document.querySelector('div'); div.addEventListener('click', function(){ console.log('点 div'); });</script>
以白话一点的方式解释,div
是 button
的父元素,也就是说当我们点选按钮时,也同样会点选到包裹着按钮的 div
,而真正的原因其实是「事件的传递阶段」—— 捕获和冒泡,所造成的。
Capture Phase 捕获 & Bubbling Phase 冒泡
在 JS 事件监听发生时,事件会沿着 HTML 结构从 Window 一层一层往下寻找目标,最终找到事件设定的目标后再依序逐层往回(外)传递,这个流程就是事件的三阶段,捕获阶段、目标阶段、及冒泡阶段。
[图片源自:W3C]
由上图我们可以看出当使用者点击 td
时,会从根节点一层一层往下寻找目标,这个过程称为 Capture Phase (捕获阶段),事件会一路传到直到找到目标 (也就是 td
),找到目标后会再返回一层一层往上,这个过程称为 Bubbling Pase
(冒泡阶段),而在这个阶段若元素也有 click
事件时,也同样会被触发。
事件冒泡所产生的问题和解决方法
事件冒泡的特性是预设会在冒泡过程中触发目标元素的上级元素之相同事件,也就是说当我们绑定许多 click
事件时,可能会因为事件冒泡而同时处发多个我们不需要的事件。
範例:
<style> .first { background-color: skyblue; width: 500px; height: 500px; } .second { background-color: pink; width: 350px; height: 350px; } .third { background-color: yellow; width: 200px; height: 200px; }</style>
<div class="first"> <div class="second"> <div class="third"></div> </div></div><script> const one = document.querySelector('.first'); const two = document.querySelector('.second'); const three = document.querySelector('.third'); one.addEventListener('click', function(){ alert("最大的"); }); two.addEventListener('click', function(){ alert("第二大的"); }); three.addEventListener('click', function(){ alert("最小的"); });</script>
在上面的範例中,我们写了三个互相嵌套的 div
和相对应的 event listener
,当点击最小的 div
时,会在冒泡的过程中依序触发第二个及最大的 div
的点击事件,而若想避免这件事情,我们就需要阻止冒泡。
阻止冒泡
语法为:event.stopPropagation();
,只要在不需要冒泡的事件中加入这行就可以成功阻止冒泡。
範例:阻止冒泡
<div class="first"> <div class="second"> <div class="third"></div> </div></div><script> const one = document.querySelector('.first'); const two = document.querySelector('.second'); const three = document.querySelector('.third'); one.addEventListener('click', function(e){ event.stopPropagation(); alert("最大的"); }); two.addEventListener('click', function(e){ event.stopPropagation(); alert("第二大的"); }); three.addEventListener('click', function(e){ event.stopPropagation(); alert("最小的"); });</script>
阻止预设行为
除了冒泡之外,在事件中还有许多预设行为,像是 <a>
连结的跳转、<form>
表单提交等等,而有时候我们并不想要触发事件的预设行为,这时候就可以利用 event.preventDefault()
进行取消。
例如下面範例,当我们想阻止跳转行为时,可以在 click
事件中加入 e.preventDefault()
const link = document.querySelector('a'); link.addEventListener('click',function(e){ e.preventDefault(); console.log('不会跳转');});