哭阿~又是我。
簡述
delegation 的意思是「代理」。
這個機制是用來解決兩個問題:
- 每個元素都要設定監聽器不會太浪費資源嗎?
- 動態產生的元素要怎麼監聽事件?
第一個問題-浪費資源
假設有 1000 個按鈕都要有點擊事件,那就要設定 1000 個 addEventListener
:
1 2 3 4 5 6 7 8 9
| 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
| document.querySelector('div') .addEventListener('click', function(e) { console.log(e.target.getAttribute('data-number')) })
|
第二個問題-動態新增的元素
如果要讓動態新增的元素也監聽事件,就得在「新增的地方」加上 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' element.classList.add('btn') element.setAttribute('data-number', currentButtonsCount) element.addEventListener('click', function(e) { console.log(e.target.getAttribute('data-number')) }) parent.appendChild(element) })
|
如果沒有加上這一段:
1 2 3
| element.addEventListener('click', function(e) { console.log(e.target.getAttribute('data-number')) })
|
動態新增的按鈕是不會有 click 效果的。
這樣子很麻煩,而且又會回到一開始講的問題(太浪費資源),所以一樣可以改用 delegation 機制。
先加上這段:
1 2 3 4 5 6
| 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' 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
| document.querySelector('div').addEventListener('click', function(e) { console.log('click capturing', e.target.getAttribute('data-index')) } , true)
document.querySelector('div').addEventListener('click', function(e) { console.log('click bubbling', e.target.getAttribute('data-index')) } , false)
|
加上 e.stopPropagation
,處於冒泡階段的監聽器就不會被觸發:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| document.querySelector('div').addEventListener('click', function(e) { console.log('click capturing', e.target.getAttribute('data-index')) e.stopPropagation() } , true)
document.querySelector('div').addEventListener('click', function(e) { console.log('click bubbling', e.target.getAttribute('data-index')) } , false)
|