# 2-6 、 瀏覽器事件傳遞機制 ###### tags: `FE102` `HTML+CSS` `2020九月第一周` `進度筆記` `Lidemy心得` 09/04 *** ## 用圖層來舉例說明 存成一個 .html 檔案: ``` <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>瀏覽器上執行 JS</title> <style> .outer { width: 500px; height: 200px; background: orange; } .inner { width: 300px; height: 100px; background: green; } </style> </head> <body> <div class='outer'> <div class='inner'> <button class='btn'>click me</button> </div> </div> <script> addEvent('.outer') addEvent('.inner') addEvent('.btn') function addEvent(className) { document.querySelector(className) .addEventListener('click', function() { console.log(className) }) } </script> </body> </html> ``` - 開啟 devtool ,點橘色圖層只會觸發 outer 。 ![](https://i.imgur.com/ZsMHek2.png) - 但點下綠色會同時觸發 inner 和 outer 。 ![](https://i.imgur.com/fAOP6Zl.png) - 按下 click me 會發現同時觸發 outer 、 inner 和 btn 三者。 ![](https://i.imgur.com/mOIpcYZ.png) - 會發現 script 的觸發對應了 div 標籤的層層疊疊順序。 *** 參考資料: [[第七週] DOM - 事件傳遞機制:捕獲與冒泡、事件代理| Yakim shu](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwiShIPRoeDrAhVCyYsBHT2MAg4QFjACegQIBBAB&url=https%3A%2F%2Fyakimhsu.com%2Fproject%2Fproject_w7_eventListener.html&usg=AOvVaw3dfU2Fh5B9-Ygf08pJGSmY) [DOM 的事件傳遞機制:捕獲與冒泡 - TechBridge 技術共筆部落格](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwiShIPRoeDrAhVCyYsBHT2MAg4QFjAAegQIAxAB&url=https%3A%2F%2Fblog.techbridge.cc%2F2017%2F07%2F15%2Fjavascript-event-propagation%2F&usg=AOvVaw1kp_TvQvTR2GQsVv7Yjk4y) [[JavaScript] Javascript 中的 DOM 事件傳遞機制:捕獲與冒泡 (capturing and bubbling) - itsems](https://medium.com/itsems-frontend/javascript-event-bubbling-capturing-794cd2d01e61) --- # 2-7 、 事件傳遞機制 part.2 ###### tags: `FE102` `HTML+CSS` `2020九月第一周` `進度筆記` `Lidemy心得` 09/04 *** ## 捕獲和冒泡 1. 捕獲階段: `Capturing_Phase` - 事件會由最上面的 Windows 傳到下 , [Event dispatch and DOM event flow -w3](https://www.w3.org/TR/DOM-Level-3-Events/#event-flow) 。 2. 目標階段: `Target_Phase` - 鎖定目標發生。 3. 冒泡階段: `Bubbling_Phase` - 將從目標發生的那一層事件,一路傳回到 Windows 。 - 先放的 EventListener 先發生,但一樣先捕獲再冒泡。 *** ## 不同階段的 EventListener 以 2-6 的 .html 修改: ``` <script> addEvent('.outer') addEvent('.inner') addEvent('.btn') function addEvent(className) { document.querySelector(className) .addEventListener('click', function() { console.log(className, '捕獲') }, true) document.querySelector(className) .addEventListener('click', function() { console.log(className, '冒泡') }, false) } </script> </body> </html> ``` - ==第一個參數是事件名稱,如上面的 click。== - ==第二個參數是 call back function , 即 ``function()`` 。== - ==第三個參數可以是一個布林變數。== - 如果布林值是 __'false'__ 就會在冒泡階段監聽; __'true'__ 的話就會放在捕獲階段上監聽。 - 如果原本都沒有傳達,預設就是 false 。 ![](https://i.imgur.com/oOs5ASN.png) 點擊綠色圖案,用 dev tool 查看 __即先捕獲再冒泡。__ --- ## 用了 preventDefault 後都不會有效果 以 2-6 的 .html 修改: ``` <body> <div class='outer'> <div class='inner'> <a href='/test'>click</a> <button class='btn'>click me</button> </div> </div> <script> addEvent('.outer') addEvent('.inner') addEvent('.btn') window.addEventListener('click', function(e) { e.preventDefault() }, true) function addEvent(className) { document.querySelector(className) .addEventListener('click', function() { console.log(className, '冒泡') }, false) } </script> </body> ``` - `e.preventDefault()` 只要被 call 出來就不會讓 click 、 link 和表單送出完全不會有效果。 --- 參考資料: 2-6 的參考資料再看一次~ --- # 2-8 、 停止往上回傳 ###### tags: `FE102` `HTML+CSS` `2020九月第一周` `進度筆記` `Lidemy心得` 09/04 - 停止冒泡或是捕獲的方法 , call function 。 *** ## ``stopPropagation()`` 以 2-7 的 script 修改 .html 檔案: ``` <script> addEvent('.outer') addEvent('.inner') function addEvent(className) { document.querySelector(className) .addEventListener('click', function() { console.log(className, '冒泡') }) } document.querySelector('.btn') .addEventListener('click', function(e) { e.stopPropagation() console.log('.btn 冒泡') }) </script> ``` ![](https://i.imgur.com/Re8LKrD.png) - 按下 click 只會觸發按鈕本身自己的事件。 - 如果在 window 層直接加入 `e.stopPropagation()` , 就永遠不會傳遞下去。 - 在一個按鈕上可以加入不只一個的 EventListener , 如: ``` document.querySelector('.btn') .addEventListener('click', function(e) { e.stopPropagation() console.log('.btn click 1') }) document.querySelector('.btn') .addEventListener('click', function(e) { e.stopPropagation() console.log('.btn click 2') }) ``` - 如果只想觸發一個事件就可以用 ``e.stopImmediatePropagation()`` , 立刻阻止其他所有的 EventListener 傳遞。 --- 參考資料: [Event.stopPropagation() - Web APIs | MDN](https://developer.mozilla.org/zh-TW/docs/Web/API/Event/stopPropagation) [Event.stopImmediatePropagation() - MDN Web Docs - Mozilla](https://developer.mozilla.org/zh-TW/docs/Web/API/Event/stopImmediatePropagation) --- # 2-9 、 案發現場有先後順序 ###### tags: `FE102` `HTML+CSS` `2020九月第一周` `進度筆記` `Lidemy心得` 09/04 ==__事件機制問題別搞混。__== ## querySelector 只會回傳第一個元素 以 .html 為例,有兩個按鈕: ``` <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>瀏覽器上執行 JS</title> <style> </style> </head> <body> <div class='outer'> <button class='btn'>One</button> <button class='btn'>Two</button> </div> <script> document.querySelector('.btn') .addEventListener('click', function() { alert(1) }) </script> </body> </html> ``` - document.querySelector() 只會回傳第一個元素。 - 即只幫第一個元素加了 EventListener 。 --- ## ``document.querySelectorAll()`` - ``document.querySelectorAll()`` 可以選到所有元素,但他底下沒有 ``.addEventListener`` 這個方法。 - 雖然不是陣列但可以當陣列來用。 --- ## 作用域相關問題 ``` <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>瀏覽器上執行 JS</title> <style> </style> </head> <body> <div class='outer'> <button class='btn'>One</button> <button class='btn'>Two</button> <button class='btn'>Three</button> <button class='btn'>Four</button> <button class='btn'>Five</button> <button class='btn'>Six</button> </div> <script> const elements = document.querySelectorAll('.btn') for(var i =0; i<elements.length; i++) { elements[i].addEventListener('click', function() { alert(i+1) }) } </script> </body> </html> ``` - function 是在點擊按鈕時觸發的,點擊的那刻 i 的值會是 `elements.length` 。 - 迴圈跑完後是 6 。 - 會以 data-value 作為自訂的屬性: --- ## 作用域相關 - 自動加入按鈕 ``` <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>瀏覽器上執行 JS</title> <style> </style> </head> <body> <div class='outer'> <button class='add-btn'>add</button> <button class='btn' data-value='1'>1</button> <button class='btn' data-value='2'>2</button> </div> <script> let num = 3 const elements = document.querySelectorAll('.btn') for(var i =0; i<elements.length; i++) { elements[i].addEventListener('click', function(e) { alert(e.target.getAttribute('data-value')) }) } document.querySelector('.add-btn').addEventListener('click', function() { const btn = document.createElement('button') btn.setAttribute('data-value', num) btn.innerText = num num++ document.querySelector('.outer').appendChild(btn) }) </script> </body> </html> ``` - 一直增加按鈕。 ![](https://i.imgur.com/gyhTUaC.png) - 但點 1 和 2 以外的按鈕不會產生視窗,宣告的 elements 只幫 1 和 2 有加入 EventListener 。 - 寫的迴圈不會自動幫你加入 EventListener 。 --- 參考資料: [Document.querySelectorAll() - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) [JavaScript 基礎知識-querySelectorAll - iT 邦幫忙::一起幫忙 ...](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwj2rLW4gOHrAhW0JaYKHdUFCV8QFjABegQIAxAB&url=https%3A%2F%2Fithelp.ithome.com.tw%2Farticles%2F10211614&usg=AOvVaw29kQegw_SvG768wialbo_h) [各瀏覽器中querySelector和querySelectorAll的實現差異分析 ...](https://codertw.com/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/293373/) --- # 2-10 、 案件委託 ###### tags: `FE102` `HTML+CSS` `2020九月第一周` `進度筆記` `Lidemy心得` 09/04 ## 直接在 class 的最上層加事件監聽 eventDelegation , 有點像是請人幫忙訂所有人便當的概念: ``` <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>瀏覽器上執行 JS</title> <style> </style> </head> <body> <div class='outer'> <button class='add-btn'>add</button> <button class='btn' data-value='1'>1</button> <button class='btn' data-value='2'>2</button> </div> <script> let num = 3 document.querySelector('.add-btn').addEventListener('click', function() { const btn = document.createElement('button') btn.setAttribute('data-value', num) btn.classList.add('btn') btn.innerText = num num++ document.querySelector('.outer').appendChild(btn) }) document.querySelector('.outer').addEventListener('click', function(e) { if (e.target.classList.contains('btn')) { alert(e.target.getAttribute('data-value')) } }) </script> </body> </html> ``` - 這樣蠻有效率,很常用,不用浪費資源 function 在監聽每個事件。 - 可以處理動態新增的情形,如上面增加按鈕。 - 透過冒泡機制往上傳,就算底下新增東西,還是可以接到事件。 --- 參考資料: [Event Delegation — 事件委派介紹與觸發委派的回呼函數| by ...](https://medium.com/@realdennis/event-delegation-%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%B4%BE%E4%BB%8B%E7%B4%B9-%E8%88%87-%E8%A7%B8%E7%99%BC%E5%A7%94%E6%B4%BE%E7%9A%84%E5%9B%9E%E5%91%BC%E5%87%BD%E6%95%B8-2990921a5ba2) [Event Delegation 事件委派| Summer。桑莫。夏天](https://cythilya.github.io/2015/07/08/javascript-event-delegation/) [為什麼有時你應該優先考慮event delegate 而不是 ... - iT 邦幫忙](https://ithelp.ithome.com.tw/articles/10120565)