萬事拜託你囉!Event-delegation 機制

哭阿~又是我。

簡述

delegation 的意思是「代理」。

這個機制是用來解決兩個問題:

  • 每個元素都要設定監聽器不會太浪費資源嗎?
  • 動態產生的元素要怎麼監聽事件?

第一個問題-浪費資源

假設有 1000 個按鈕都要有點擊事件,那就要設定 1000 個 addEventListener

1
2
3
4
5
6
7
8
9
// 1000 個按鈕
const buttons = document.querySelectorAll('.btn')
// 幫所有按鈕加上監聽器
for (let i=0; i<buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('click')
}
)
}

有沒有更好的做法?

當然有,請你回想「事件傳遞機制」。

當我們點下按鈕時,事件會先從 window 一路向下傳遞到父層,再到按鈕(target),接著再一路往上傳回 window

所以把監聽器掛在「按鈕的父層」上一樣可以觸發事件,因此「一個」addEventListener 就夠了。

HTML:

1
2
3
4
5
6
7
8
<div class="div">
<button class="btn" data-number="1">button</button>
<button class="btn" data-number="2">button</button>
<button class="btn" data-number="3">button</button>
<button class="btn" data-number="4">button</button>
<button class="btn" data-number="5">button</button>
<button class="btn" data-number="6">button</button>
</div>
1
2
3
4
5
6
// 只要在 div 中點擊都會被觸發的監聽器
document.querySelector('div')
.addEventListener('click', function(e) {
// 印出按鈕的編號
console.log(e.target.getAttribute('data-number'))
})

delegation-01

第二個問題-動態新增的元素

如果要讓動態新增的元素也監聽事件,就得在「新增的地方」加上 addEventListener

HTML:

1
2
3
4
5
<div class="div">
<button class="add-btn">add button</button>
<button class="btn" data-number="1">button</button>
<button class="btn" data-number="2">button</button>
</div>

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 父節點
const parent = document.querySelector('div')
// 目前有幾個按鈕
let currentButtonsCount = 2
// 新增按鈕
document.querySelector('.add-btn').addEventListener('click',
function(e) {
// 更新按鈕數量
currentButtonsCount++
// 建立新元素
const element = document.createElement('button')
// 填入文字
element.innerText = 'button'
// 加上 class
element.classList.add('btn')
// 加上編號
element.setAttribute('data-number', currentButtonsCount)
// 加上監聽器
element.addEventListener('click', function(e) {
console.log(e.target.getAttribute('data-number'))
})
// 插入新元素
parent.appendChild(element)
})

delegation-02

如果沒有加上這一段:

1
2
3
element.addEventListener('click', function(e) {
console.log(e.target.getAttribute('data-number'))
})

動態新增的按鈕是不會有 click 效果的。

這樣子很麻煩,而且又會回到一開始講的問題(太浪費資源),所以一樣可以改用 delegation 機制。

先加上這段:

1
2
3
4
5
6
// 監聽 div 底下的所有按鈕
document.querySelector('div')
.addEventListener('click', function(e) {
// 印出按鈕的編號
console.log(e.target.getAttribute('data-number'))
})

在改一下新增按鈕的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 父節點
const parent = document.querySelector('div')
// 目前有幾個按鈕
let currentButtonsCount = 2
// 新增按鈕
document.querySelector('.add-btn').addEventListener('click',
function(e) {
// 更新按鈕數量
currentButtonsCount++
// 建立新元素
const element = document.createElement('button')
// 填入文字
element.innerText = 'button'
// 加上 class
element.classList.add('btn')
// 加上編號
element.setAttribute('data-number', currentButtonsCount)
// 插入新元素
parent.appendChild(element)
})

補充-讓 delegation 的元素監聽捕獲跟冒泡有差嗎?

在沒有設定 e.stopPropagation 的情況下是沒差的,不過一樣會遵循「先捕獲,再冒泡」的原則:

1
2
3
4
5
6
7
8
<div class="div">
<button class="add-btn">add button</button>
<button class="btn" data-index="1">button</button>
<button class="btn" data-index="2">button</button>
<button class="btn" data-index="3">button</button>
<button class="btn" data-index="4">button</button>
<button class="btn" data-index="5">button</button>
</div>

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
// 讓 div 監聽捕獲階段
document.querySelector('div').addEventListener('click',
function(e) {
console.log('click capturing', e.target.getAttribute('data-index'))
}
, true)
// 讓 div 監聽冒泡階段
document.querySelector('div').addEventListener('click',
function(e) {
console.log('click bubbling', e.target.getAttribute('data-index'))
}
, false)

delegation-03

加上 e.stopPropagation,處於冒泡階段的監聽器就不會被觸發:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 讓 div 監聽捕獲階段
document.querySelector('div').addEventListener('click',
function(e) {
console.log('click capturing', e.target.getAttribute('data-index'))
// 阻止事件傳遞
e.stopPropagation()
}
, true)
// 讓 div 監聽冒泡階段
document.querySelector('div').addEventListener('click',
function(e) {
console.log('click bubbling', e.target.getAttribute('data-index'))
}
, false)

delegation-04

再次理解什麼是作用域? preventDefault 的小知識
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×