---
# System prepended metadata

title: 2-6 、 瀏覽器事件傳遞機制
tags: [Lidemy心得, 2020九月第一周, FE102, HTML+CSS, 進度筆記]

---

# 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)  
