iT邦幫忙

2022 iThome 鐵人賽

DAY 7
0
自我挑戰組

菜鳥前端修練之旅系列 第 7

Day 07 | 事件冒泡與捕獲

  • 分享至 

  • xImage
  •  

JS 中的事件冒泡 & 捕獲是前端必須要了解的重要原理,不然會發生 怎麼點了一個元素其他事件跟著一起噴出來 的奇怪問題。

事件機制

以下面的 HTML 為例:

<div class="outer">
    <div class="inner"></div>
</div>

inner 被包在 outer 裡面,所以當我們點擊 inner 時,究竟是只點到 inner,還是連 outer 一起點了?

實際上是兩個都有點到。

上圖可以看到,當我們點擊 <td> 時,事件會從 Window 一路往下傳、傳到 <td> 後再一路傳回去 Window,所以上面的例子才會說兩個都有點擊到。

事件捕獲 & 冒泡

上面提到的「從 Window 一路往下傳」,這個階段就叫做事件捕獲(Event Capturing)。相反的從 <td> 一路往上到 Window 的階段就是事件冒泡(Event Bubbling)。

所以常常會看到一個重要的口訣:

先捕獲,再冒泡。

看到這邊我們就知道一個事件會有三個階段:

  • 捕獲階段(Capturing phase)
  • 目標階段(Target phase)
  • 冒泡階段(Bubbling phase)

addEventListener 決定階段

我們常用的 addEventListener 能夠決定要在捕獲還是冒泡階段執行某個事件。決定的方法是在 addEventListener 的第三個選項(capture)加入布林。

outer.addEventListener('click', () => {
  console.log("outer")
}, {capture: true})

// 或是寫成以下
outer.addEventListener('click', () => {
  console.log("outer")
}, true)

使用 true 的話,表示 listener 是在捕獲階段執行、false 則相反。

如果不特別設定(空白)的話,預設就會是 false

阻止事件冒泡

有時候我們希望點擊的時候只觸發自己本身的事件,例如:

outer.addEventListener('click', (e) => {
  console.log('outer')
})

inner.addEventListener('click', (e) => {
  console.log('inner')
})

結果按下 inner 後,兩個 log 都出來了,原因是因為事件冒泡。要解決這個也很簡單,使用 event.stopPropagation() 就能阻止事件往下一個節點前進。

inner.addEventListener('click', (e) => {
  console.log('inner')
  event.stopPropagation()
})

大多數事件都會冒泡。

為什麼用「大多數」?因為像 focusblur 等事件是不會冒泡的。

事件代理

我們在替 <ul> 內的 <li> 個別添加事件時,會這樣寫:

// HTML
<ul>
    <li>首頁</li>
    <li>商品頁</li>
    <li>搜尋頁</li>
</ul>
// JS
const el = document.querySelectorAll("li");

// 針對各個 li 添加事件
el.forEach(item => {
    item.addEventListener("click", (e) => {
        console.log(e.target);
    })
});

雖然可以成功的替每個元素增加事件,但如果我們在 <ul> 中新增了新的 <li>,新增的元素就吃不到事件了。

所以在理解了事件機制後,我們可以將 JS 的程式碼改為:

const list = document.querySelector('ul');

list.addEventListener("click", (e) => {
    // 判斷點擊到 li 標籤後觸發事件
    if(e.target.tagName === "LI"){
        // ...
    }
});

除了更方便管理外、也解決了新增 DOM 抓不到事件的問題。

參考資料


上一篇
Day 06 | 加強 SCSS
下一篇
Day 08 | 傳值與傳址
系列文
菜鳥前端修練之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言