---
# System prepended metadata

title: '[FE102] 前端必備：JavaScript(筆記)'
tags: [' [FE102] 前端必備：JavaScript', Lidemy學院]

---

# [FE102] 前端必備：JavaScript(筆記)
###### tags: `Lidemy學院` ` [FE102] 前端必備：JavaScript`
# 課程簡介
## 課程簡介
基本上在網頁上頁JavaScript時，所關注的面向，有3個 : 
1. 介面 : 如何改變
如何使用 JavaScript 去改變，用 html 跟 css ，所寫出來的 UI，例如改變元素，新增元素。

2. 事件 : 如何監聽事件並做出反應
比如，按一個按紐去新增元素，或是按按鈕改變背景顏色，如果會介面跟事件，能做出7-8成的東西。

3. 資料 : 如何跟伺服器交換資料
或是如何讓資料再瀏覽器保存下來。

> 基本上如果會以上3點，能寫出任何的程式，網頁的部分，不外乎就這3點。
___
# JavaScript 與瀏覽器的溝通
## 執行 JavaScript 的一百種方式
在執行 JavaScript 前，要思考 JavaScript 能在哪裡執行，重點是==在哪裡執行 ?==

第一種方式 : `<script>`放在任何地方都可以，但通常放在`</body>`前，
```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <script>
      alert('hello')
    </script>
  </head>
  <body></body>
</html>
```
第二種方式 : 將 JS 檔，寫成一個檔案，在引入html檔中
```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <script src="./index.js"></script>
  </head>
  <body></body>
</html>
```
```
//index.js
alert('hello')
```

探討 : 在 Node.js 與 瀏覽器跑 JS 的差別 ?
1. 在跑 JS 時，都是一樣的，在瀏覽器中， Node.js 有些語法並不支援，Node.js 與 瀏覽器跑，是兩個不同的執行環境，所以可能這個支援，那個不支援，所以有些用法在 Node.js 中是可以的，在瀏覽器中不行的，反過來也是一樣
___
## DOM 是什麼？
[文件物件模型 (DOM)](https://developer.mozilla.org/zh-TW/docs/Web/API/Document_Object_Model)，（Document Object Model, DOM），簡單的解釋是將 Document 轉換成 Object，DOM 是指在 html 的那些內容，可以想像把 html 文件的這結構，變成很多節點，並有階層關係，可以把標籤轉換成下圖，轉換成下圖其實感覺就跟物件差不多，所以 JS 怎麼改變介面，就是透過 DOM ，會透過 JS 去拿到 DOM ，意思就是可以拿到這個節點(元素)，並去做改變，儘管你寫的是 JS ，瀏覽器會幫助你去改變瀏覽器上的東西。

==DOM 是什麼？ 瀏覽器提供這個橋梁(DOM) ，讓 JS 能改變畫面的東西。==

![](https://i.imgur.com/pTT13LC.png)
___
## 如何選到想要的元素：getElement
`document` 是瀏覽器提供的物件，有許多function，可以使用。

HTMLCollection : 用起來像是陣列，但並不是陣列，算是特殊形態的陣列，使用起來並沒有甚麼差。

NodeList : 跟 HTMLCollection 意思差不多，像是陣列，但並不是陣列。

在使用上，`querySelector`跟`querySelectorAll`，較常使用。

1. `getElementsByTagName` : 取標籤

```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div>
      hello~~
    </div>
    <div>
      yoyoyo~~
    </div>
    <script>
      const elements = document.getElementsByTagName('div')
      console.log(elements)
      console.log(elements[0])
    </script>
  </body>
</html>
```
2. `getElementsByClassName` : 取Class

```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div class="block">
      hello~~
    </div>
    <div class="block">
      yoyoyo~~
    </div>
    <script>
      const elements = document.getElementsByClassName('block')
      console.log(elements)
      console.log(elements[0])
    </script>
  </body>
</html>
```
3. `getElementById` : 取Id，在使用上，會直接找到元素，Id在使用上是唯一的。

```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div id="block">
      hello~~
    </div>
    <div>
      yoyoyo~~
    </div>
    <script>
      const elements = document.getElementById('block')
      console.log(elements)
    </script>
  </body>
</html>
```
4. `querySelector` : 取css的選擇器，只會回傳第一個取到的元素

```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div id="block">
      hello~~
    </div>
    <div>
      yoyoyo~~
    </div>
    <script>
      const elements = document.querySelector('div')
      console.log(elements)
    </script>
  </body>
</html>
```
5. `querySelectorAll` : 取css的選擇器，會回傳所有符合的元素

```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div id="block">
      hello~~
    </div>
    <div>
      yoyoyo~~
    </div>
    <script>
      const elements = document.querySelectorAll('div')
      console.log(elements)
    </script>
  </body>
</html>
```
___
## 改變元素的 CSS
1. 所選元素`.style`.屬性 = '值' : 可以去改變元素，通常不太會如此使用，會先寫好class，在去改變。
```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div id="block">
      hello~~
    </div>
    <script>
      const element = document.querySelector('#block')
      element.style.background = 'red'
      element.style.padding = '10px'
      element.style['padding-top'] = '30px'
      element.style.paddingBottom = '40px'
    </script>
  </body>
</html>
```
___
## 改變元素的 Class
在改變元素中通常是，改變元素的 Class，在用 Class 去負責不同的 style 。 

1. 新增class : 所選元素.classList.add('className')
2. 移除class : 所選元素.classList.remove('className')
3. 開關class，有此class，會移除，無此class，會新增 : 所選元素.classList.toggle('className')，適合使用在許多地方

```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .action {
        background-color: red;
      }
    </style>
  </head>
  <body>
    <div id="block" class="main box1">
      hello~~
    </div>
    <script>
      const element = document.querySelector('#block')
      element.classList.add('action')
      element.classList.remove('main')
      element.classList.toggle('box')
      element.classList.toggle('box1')
    </script>
  </body>
</html>ement.classList.remove('main')
    </script>
  </body>
</html>
```
___
## 改變內容：inner、outer 的 HTML 與 text
1. `innerText` : 改變內容文字，只會抓到文字
2. `innerHTML` : 將標籤中的內容都抓出來
3. `outerHTML` : 將標籤自己與其內容都抓出來

常用 : `innerText`，改變內容文字。
```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div id="block">
      yoyo~~
      <a>hello~~</a>
    </div>
    <script>
      const element = document.querySelector('#block > a')
      console.log(element.innerText)
      element.innerText = '!!!'
      console.log(element.innerHTML)
      console.log(element.outerHTML)
      element.outerHTML = '<h1>h1</h1>'
    </script>
  </body>
</html>
```
___
## 插入與刪除元素：appendChild 與 removeChild
在操作插入與刪除元素時，需要知道父層是誰。
1. 刪除元素 : `removeChild`
2. 插入元素 : `appendChild`
3. 新增元素 : `createElement`
4. 新增文字 : `createTextNode`

```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div id="block">
      yoyo~~
      <a>hello~~</a>
    </div>
    <script>
      const element = document.querySelector('#block')
      element.removeChild(document.querySelector('a'))
      const item = document.createElement('div')
      item.innerText = '123'
      element.appendChild(item)
      const item2 = document.createTextNode('Text')
      element.appendChild(item2)
    </script>
  </body>
</html>
```
___
# JavaScript 網頁事件處理
## eventListener 與 callback function
### event Listener (事件監聽)
在這 element 上，新增(add)一個 event Listener (事件監聽)，叫做 click ，重點是按完後要做甚麼事情，就是 onClick 這 function，所以當 click 事件發生時，瀏覽器會去觸發 onClick 這 function ，這 function 叫做  callback function (回呼函式) ，所以在這 function 中，可以做任何想做的事，通常都是以比較偷懶的方式去寫，addEventListener( )，第 2 參數，要放 function ，所以可以直接寫 function 在裡面，這時這 function 是沒有名稱的，所以叫做匿名函式，它跟 callback function 一點關係都沒有，叫匿名函式只是沒有名字而已，任何沒有名字 function ，都叫匿名函式，有許多事件可以做監聽，需要可以去 google。
```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>

  <body>
    <div id="block">
      yoyo~~
      <a>hello~~</a>
    </div>
    <script>
      const element = document.querySelector('#block')
      element.addEventListener('click', onClick)

      function onClick() {
        alert('click!')
      }
    </script>
  </body>
</html>
```
```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>

  <body>
    <div id="block">
      yoyo~~
      <a>hello~~</a>
    </div>
    <script>
      const element = document.querySelector('#block')
      element.addEventListener('click', function () {
        alert('click!')
      })
    </script>
  </body>
</html>
```

___
## 詳細講解 callback function
### callback function : 
當有觸發事情，才會去做事的 function 大約可以叫做 callback function，傳入的參數是函式，就可以叫做 callback function。

callback function 的意思其實就是：「當某事發生的時候，請利用這個 function 通知我」

在使用 callback function 時，有一個初學者很常犯的錯誤一定要特別注意。都說了傳進去的參數是 callback function，是一個「function」，不是 function 執行後的結果（除非你的 function 執行完會回傳 function，這就另當別論）。

[JavaScript 中的同步與非同步（上）：先成為 callback 大師吧！](https://blog.techbridge.cc/2019/10/05/javascript-async-sync-and-callback/)
___
## event(e) 是什麼碗糕？
在上述範例中，其實在 eventListener 中，可以拿到許多資訊，瀏覽器在呼叫你的 function 時，會傳遞進來一個參數，通常會叫 event ，或最多的簡寫是 e ，名稱是可以隨便取的，只是一個參數而已。

開啟瀏覽器去點擊， 可以開 console.log ，出來看到點了哪裡，這 e 是一個 MouseEvent，可以知道看到 x 跟 y ，比較重要 的是 tarage 是指點到的元素，
```
//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>

  <body>
    <div id="block">
      yoyo~~
      <a>hello~~</a>
    </div>
    <script>
      const element = document.querySelector('#block')
      element.addEventListener('click', function (e) {
        console.log(e)
      })
    </script>
  </body>
</html>
```
![](https://i.imgur.com/HS47iGA.png)

可以用 e.tarage，知道點到的元素

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>

  <body>
    <div id="block">
      yoyo~~
      <a>hello~~</a>
    </div>
    <script>
      const element = document.querySelector('#block')
      element.addEventListener('click', function (e) {
        console.log(e.target)
      })
    </script>
  </body>
</html>
```
![](https://i.imgur.com/a0S3VQX.png)

所以在 e 中，可以取到許多資訊，e 是瀏覽器給的資訊，可以利用此資訊，取的跟事件相關的東西。

以下為 input 做示範 ，keydown: 
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
  </head>
  <body>
    <div id="block">
      yoyo~~
      <input type="text" name="" id="" />
      <a>hello~~</a>
    </div>
    <script>
      const element = document.querySelector('input')
      element.addEventListener('keydown', function (e) {
        console.log(e.key)
      })
    </script>
  </body>
</html>
```
![](https://i.imgur.com/sD2tdo8.png)

示範 : 按按鈕切換背景顏色的功能

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .active {
        background: red;
      }
    </style>
  </head>
  <body>
    <div id="block">
      yoyo~~
      <input type="text" name="" id="" />
      <a>hello~~</a>
      <button class="change-btn">change</button>
    </div>
    <script>
      const element = document.querySelector('.change-btn')
      element.addEventListener('click', function (e) {
        document.querySelector('body').classList.toggle('active')
      })
    </script>
  </body>
</html>
```
___
## 表單事件處理 onSubmit
範例 : 
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .active {
        background: red;
      }
    </style>
  </head>
  <body>
    <form class="login-form">
      <div>
        username:
        <input name="username" />
      </div>
      <div>
        password:
        <input name="password" type="password" />
      </div>
      <div>
        password again:
        <input name="password2" type="password" />
      </div>
      <input type="submit" />
    </form>

    <script>
      const element = document.querySelector('.login-form')
      element.addEventListener('submit', function (e) {
        alert('submit!!')
      })
    </script>
  </body>
</html>
```

preventDefault() : 瀏覽器提供的 function ，可以取消事件預設行為 ，Submit 預設行為就是送出表單。

可以使用在哪裡，判斷 password 跟 password again ，是否相同 : 

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .active {
        background: red;
      }
    </style>
  </head>
  <body>
    <form class="login-form">
      <div>
        username:
        <input name="username" />
      </div>
      <div>
        password:
        <input name="password" type="password" />
      </div>
      <div>
        password again:
        <input name="password2" type="password" />
      </div>
      <input type="submit" />
    </form>

    <script>
      const element = document.querySelector('.login-form')
      element.addEventListener('submit', function (e) {
        const input1 = document.querySelector('input[name = password]')
        const input2 = document.querySelector('input[name = password2]')
        if (input1.value !== input2.value) {
          alert('密碼不同')
          e.preventDefault()
        }
      })
    </script>
  </body>
</html>
```
___
## 阻止預設行為：preventDefault
e.preventDefault : 阻止瀏覽器預設行為，其實除了表單送出外，還可以用在其他地方，以下舉例 : 

[[筆記][JavaScript]所謂的「停止事件」到底是怎麼一回事？](https://ithelp.ithome.com.tw/articles/10198999)

`<a>` 超連結 : 取消連到某個地方

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .active {
        background: red;
      }
    </style>
  </head>
  <body>
    <form class="login-form">
      <div>
        username:
        <input name="username" />
      </div>
      <div>
        password:
        <input name="password" type="password" />
      </div>
      <div>
        password again:
        <input name="password2" type="password" />
      </div>
      <input type="submit" />
      <a href="/test">link</a>
    </form>

    <script>
      const element = document.querySelector('.login-form')
      element.addEventListener('click', function (e) {
        e.preventDefault()
      })
    </script>
  </body>
</html>
```

`<input>` 輸入框 : 無法送出 e ，以下為範例

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .active {
        background: red;
      }
    </style>
  </head>
  <body>
    <form class="login-form">
      <div>
        username:
        <input name="username" />
      </div>
      <div>
        password:
        <input name="password" type="password" />
      </div>
      <div>
        password again:
        <input name="password2" type="password" />
      </div>
      <input type="submit" />
      <a href="/test">link</a>
    </form>

    <script>
      const element = document.querySelector('input[name = username]')
      element.addEventListener('keypress', function (e) {
        if (e.key === 'e') {
          e.preventDefault()
        } else {
          console.log(e.key)
        }
      })
    </script>
  </body>
</html>
```

![](https://i.imgur.com/c3OMPAT.png)

___
## 比複雜更複雜的事件傳遞機制

瀏覽器 事件傳遞機制 : 點擊區塊的影響，是有順序的，會先從近的開始。以下 console.log()，是在 冒泡階段

[DOM 的事件傳遞機制：捕獲與冒泡](https://blog.techbridge.cc/2017/07/15/javascript-event-propagation/)

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .outer {
        width: 500px;
        height: 200px;
        background: red;
      }
      .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>
```
![](https://i.imgur.com/TSu5TOS.png)

___
## 事件傳遞機制詳解：捕獲與冒泡

以下圖去理解，事件傳遞機制。

問題1 : 如何將 event Listener (事件監聽) ，掛在不同階度。

`addEventListener( )`，有第3參數，可以放 bool 
* false : 放在冒泡階段上。
* true : 放在捕獲階段上。

在 target Phase 上比較特別，會先看 哪個 event Listener 在前，就執行哪個。

![](https://i.imgur.com/ihpy9hm.png)

以下為範例 : 點擊 click me ，開啟 console 
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .outer {
        width: 500px;
        height: 200px;
        background: red;
      }
      .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, '捕獲')
          },
          true,
        )
        document.querySelector(className).addEventListener(
          'click',
          function () {
            console.log(className, '冒泡')
          },
          false,
        )
      }
    </script>
  </body>
</html>
```
![](https://i.imgur.com/ETX98OE.png)

___
## 別向上級回報：stopPropagation
舉例 : 冒泡階段 就像是 向上級回報一樣，那如果不想向上回報時，只想自己可以接收到事件就好，不想到冒泡階段，讓事件傳遞下去。

* stopPropagation()
* stopImmediatePropagation()

它們的共同點：
都是阻止後續的偵聽行為，即能阻擋掉事件流中事件的冒泡，簡而言之就是讓後面的偵聽都不執行；

不同點：
是擁有事件監聽函數的當前的節點是否執行該函數，stopPropagation()方法阻止事件對象移到到另一個節點上，但是允許當前節點的其他事件監聽函數執行，而stopImmediatePropagation()方法不僅阻止事件從當前節點移動到另一個節點上，它還不允許當前節點的其他事件監聽函數執行。


舉例 : 如下，可以在 botton ，下不只一個監聽，如使用 e.stopPropagation()，多個監聽都會有效過，如下 e.stopImmediatePropagation()，只會監聽一個事件，其他都沒效果。

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      .outer {
        width: 500px;
        height: 200px;
        background: red;
      }
      .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')
      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>
  </body>
</html>
```
![](https://i.imgur.com/wsMIPYk.png)

___
## 新手 100% 會搞錯的事件機制問題

程式是一行一行執行，在用 querySelectorAll 時，這個 elements 是只有那兩個按鈕 ，動態新增的按鈕，並沒有 事件機制。

data-value : 存放資訊 。

動態新增的元素，不會加入先寫好的事件監聽。
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</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>
```
___
## 欸等等幫我拿餐點：event delegation
event delegation (事件代理)，請回憶起 事件傳遞機制詳解：捕獲與冒泡
* 常用，比較有效率，節省許多資源，不用加那麼多 function ，浪費那麼多 function，去監聽每一個事件，因為每一個是事件做的事情都差不多，所以可以只使用一個 EventListener 去做管理就好
* 可以處理，動態新增的情形，動態還是可以抓到，特過冒泡機制，傳遞上去，所以即使底下新增的元素，還是可以接到他的事件。

在範例中，將在 `btn` 的事件監聽，加在 `.outer` 上，稱事件代理。
* `contains('n')` : 判斷，`classList`，是否有 n 這 class。

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</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.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>
```
___
## 綜合示範：簡易密碼產生器
備註：

影片裡的程式碼其實有一個 bug，那就是儘管你沒有勾選英文字母，還是會產生出來，那是因為 getChar 這個 function 最後忘記加上 return ''

所以預設會 return undefined，被轉成文字之後產生出的結果就會有 undefined 或是部分字母，造成程式的 bug

getChar 的最後加上 return '' 就行了

範例 :
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      body {
        font-size: 40px;
      }
      .result {
        background: rgba(0, 0, 0, 0.5);
      }
    </style>
  </head>
  <body>
    <div class="app">
      <div>
        <label for="">
          <input type="checkbox" name="en" />
          英文
        </label>
      </div>
      <div>
        <label for="">
          <input type="checkbox" name="num" />
          數字
        </label>
      </div>
      <div>
        <label for="">
          <input type="checkbox" name="sp" />
          特殊符號
        </label>
      </div>
      <div>
        <button class="btn-generate">產生</button>
        <div class="result"></div>
      </div>
    </div>
    <script>
      document
        .querySelector('.btn-generate')
        .addEventListener('click', function () {
          let availableChar = ''
          if (document.querySelector('input[name = en]').checked) {
            availableChar += 'abcdefghijklmnopqrstuvwxyz'
          }
          if (document.querySelector('input[name = num]').checked) {
            availableChar += '0123456789'
          }
          if (document.querySelector('input[name = sp]').checked) {
            availableChar += '!@#$%^&*()'
          }
          let result = ''
          for (let i = 0; i < 10; i++) {
            //0 ~ availableChar.length - 1
            const number = Math.floor(Math.random() * availableChar.length)
            result += availableChar[number]
          }
          document.querySelector('.result').innerText = result
        })
    </script>
  </body>
</html>
```
優化 : 
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      body {
        font-size: 40px;
      }
      .result {
        background: rgba(0, 0, 0, 0.5);
      }
    </style>
  </head>
  <body>
    <div class="app">
      <div>
        <label for="">
          <input type="checkbox" name="en" />
          英文
        </label>
      </div>
      <div>
        <label for="">
          <input type="checkbox" name="num" />
          數字
        </label>
      </div>
      <div>
        <label for="">
          <input type="checkbox" name="sp" />
          特殊符號
        </label>
      </div>
      <div>
        <button class="btn-generate">產生</button>
        <div class="result"></div>
      </div>
    </div>
    <script>
      function getChar(name, char) {
        if (document.querySelector('input[name = ' + name + ']').checked) {
          return char
        }
        return
      }
      document
        .querySelector('.btn-generate')
        .addEventListener('click', function () {
          let availableChar = ''
          availableChar += getChar('en', 'abcdefghijklmnopqrstuvwxyz')
          availableChar += getChar('num', '0123456789')
          availableChar += getChar('sp', '!@#$%^&*()')
          let result = ''
          for (let i = 0; i < 10; i++) {
            //0 ~ availableChar.length - 1
            const number = Math.floor(Math.random() * availableChar.length)
            result += availableChar[number]
          }
          document.querySelector('.result').innerText = result
        })
    </script>
  </body>
</html>
```
再優化 :
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      body {
        font-size: 40px;
      }
      .result {
        background: rgba(0, 0, 0, 0.5);
      }
    </style>
  </head>
  <body>
    <div class="app">
      <div>
        <label>
          <input
            type="checkbox"
            name="en"
            data-char="abcdefghijklmnopqrstuvwxyz"
          />
          英文
        </label>
      </div>
      <div>
        <label>
          <input type="checkbox" name="num" data-char="0123456789" />
          數字
        </label>
      </div>
      <div>
        <label>
          <input type="checkbox" name="sp" data-char="!@#$%^*()" />
          特殊符號
        </label>
      </div>
      <div>
        <button class="btn-generate">產生</button>
        <div class="result"></div>
      </div>
    </div>
    <script>
      document
        .querySelector('.btn-generate')
        .addEventListener('click', function () {
          let availableChar = ''
          const elements = document.querySelectorAll('input[type=checkbox]')
          for (let i = 0; i < elements.length; i++) {
            if (elements[i].checked) {
              availableChar += elements[i].getAttribute('data-char')
            }
          }
          let result = ''
          for (let i = 0; i < 10; i++) {
            //0 ~ availableChar.length - 1
            const number = Math.floor(Math.random() * availableChar.length)
            result += availableChar[number]
          }
          document.querySelector('.result').innerText = result
        })
    </script>
  </body>
</html>
```
___
## 綜合示範：動態表單通訊錄
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style>
      body {
        font-size: 40px;
      }
      .result {
        background: rgba(0, 0, 0, 0.5);
      }
    </style>
  </head>
  <body>
    <div class="app">
      <div>
        <button class="add-btn">新增聯絡人</button>
      </div>
      <div class="contacts">
        <div class="row">
          姓名:
          <input name="name" />
          電話:
          <input name="phone" />
          <button class="delete">刪除</button>
        </div>
      </div>
    </div>
    <script>
      document.querySelector('.add-btn').addEventListener('click', function () {
        const div = document.createElement('div')
        div.classList.add('row')
        div.innerHTML = `姓名:
                                  <input name="name" />
                                  電話:
                                  <input name="phone" />
                                  <button class="delete">刪除</button>`
        document.querySelector('.contacts').appendChild(div)
      })
      document
        .querySelector('.contacts')
        .addEventListener('click', function (e) {
          if (e.target.classList.contains('delete')) {
            document
              .querySelector('.contacts')
              .removeChild(e.target.closest('.row'))
          }
        })
    </script>
  </body>
</html>
```
___
# 如何在瀏覽器上儲存資料？
## 網頁的資料都存在哪裡？為什麼換台電腦購物車就清空了？
瀏覽器的資料儲存，我們可以利用js把資料做儲存，把想存的資料存在瀏覽器中，所以我們可以存資料的地方不只一個，瀏覽器會有一個地方拿來存你的資料，因為一個最基本的問題，http的狀態是如何，也就是說它不知道，上一個人跟現在是不是同一個人，所以我們需要一些方法，將資料自動帶到server去，讓他可以認識我們，所以這也是，為甚麼有些購物網站，我把一些商品加入購物車，換了個b電腦，購物車商品就沒有了，代表商品是存在a電腦上，不是存在後端，如果是存在後端的資料庫，不管事用哪一台電腦，都是抓地到的，如果換了一台電腦購物車就清空，代表是將資訊純在瀏覽器裡的
___
## 最古老的方式：Cookie


[Cookie - wiki](https://zh.wikipedia.org/wiki/Cookie) : 其實是個小型文字檔，會自動帶到 server。

我們可以用js將資料寫在 Cookie ， server 也可以透過，http 的 response ，把資料寫進 Cookie， server 有一個 response ，叫做 Set-Cookie : ...，裡面會放一些資訊，只要瀏覽器看到 Set-Cookie，就會將 Cookie 給寫入，所以的 request 都會把 瀏覽器的 Cookie 給帶上去，為了讓 瀏覽器 辨識身分 ，辨識身分指的意思，有時候會被用在 廣告追蹤 ，可以打開隨便一個網站，開啟 檢查，的 Application，Storage 裡 就有 Cookie ，裡面毀有一堆 domain ，裡面有些 id 就是拿來追蹤你用的，當你拜訪網站時，這網站插入了些 google 程式碼，那 google 就知道，你來哪一些網站，google 就能用這去辨認 剛剛跟現在是否是同一個人。

所以 Cookie ，是會用在 身分驗證上。

舉例 : 登入，如何判斷是同一個人，第一次拜訪某網站時，server 發通行證會放在 Cookie，第2次拜訪時，瀏覽器 都會資自動帶上 Cookie，那 server 就知道是同一個人。

Cookie 會放很多 domain，瀏覽器會帶跟你在拜訪的網站相關 Cookie，給 你在拜訪的 server。
___
## 最推薦的儲存方式：local storage
Cookie 像是伺服器跟瀏覽器的一個溝通。實際上伺服器，也可以設定 Cookie，所以一般來說，我們想要 儲存資訊，跟 server沒關時，其實會用另一個叫 local storage。

範例 : 按按鈕，將資訊存入 local storage，開啟 檢查，的 Application，local storage 裡可以看到資訊，只能存字串。
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style></style>
  </head>
  <body>
    <div class="app">
      <input type="text" class="text" />
      <button class="add-btn">储存</button>
    </div>
    <div class="app1"></div>
    <script>
      const oldValue = window.localStorage.getItem('text')
      document.querySelector('.text').value = oldValue
      document.querySelector('button').addEventListener('click', function () {
        const value = document.querySelector('.text').value
        window.localStorage.setItem('text', value)
      })
    </script>
  </body>
</html>
```
___
## 一閃即逝：session storage

session storage 跟 Cookie 用法是一樣的，session 是一種有時間概念的意思在。

特點是拿來存些短期的資訊，如換分頁， session storage 的資訊會消失。

範例 : 將上方程式，改成 session storage，其他都不用變。開新分頁，資訊就不見了，只能短期方資料。
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>FE102</title>
    <style></style>
  </head>
  <body>
    <div class="app">
      <input type="text" class="text" />
      <button class="add-btn">储存</button>
    </div>
    <div class="app1"></div>
    <script>
      const oldValue = window.sessionStorage.getItem('text')
      document.querySelector('.text').value = oldValue
      document.querySelector('button').addEventListener('click', function () {
        const value = document.querySelector('.text').value
        window.sessionStorage.setItem('text', value)
      })
    </script>
  </body>
</html>
```

___
# 中場總結
## 學了這些之後，可以幹嘛？
發揮想像力，學會以上技術基本上只要不跟網路相關，都能做出來，現在可以監聽是監，可以改變UI，所以這兩件事，就是網頁的元素，像上學會這兩項，可以寫出些遊戲，只是欠缺些網路資料交換的能力，其他都寫得出來，貪吃蛇，表單驗證等等...
___
## 遺漏的最後一塊拼圖：資料的交換
已學會，如何用JS改變UI，如何用JS去監聽事件，然後做出反應，還沒學到的事，怎麼跟 server 做資料交換，例如送資料給 server ， server 在返回資料給你，JS關注的要點都會了，只要會這3點，所有看的網頁跟JS有關的都做得出來。
___
# 網頁與伺服器的溝通
## 幫你喚回記憶：API 與網頁伺服器
Client 發送 request 到 Server，Server 回傳 response ，response 有可能是 .html 檔案或是 JSON 格式檔案。

瀏覽器上輸入網址(Client 發送 request)，顯示的畫面(Server 回傳 response(.html 檔案))。
___
## 用 node.js 呼叫 API 與在網頁上呼叫的根本差異是什麼？
差別 : 
使用 node.js 發送 request ，到 Server ，這中間過程是沒有人限制你的。

使用瀏覽器上的JS 發送 request，到 Server ，中間會被瀏覽器限制，第一個是瀏覽器可能會阻止你做一些事情，第二個是瀏覽器會幫我們加一些東西，例如，你的瀏覽器的版本是甚麼，會加些而外的資訊，Server 收到的 request ，就會有而外的資訊，那再用 node.js 時，你不加的資訊，就不會發送到 Server。

![](https://i.imgur.com/k8o9RDP.png)

![](https://i.imgur.com/i9Ysj4Y.png)
___
## 傳送資料的第一種方式：表單 form
範例 : 使用 form 傳送資料 跟 JS 沒關係
```
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>form</title>
  </head>
  <body>
    <form method="GET" action="https://google.com">
      username:
      <input name="username" />
      <input type="submit" />
    </form>
  </body>
</html>
```
再用 GET，發送時，資訊會帶到網址後面，POST，發送時，資訊會帶到body裡。
發送出去後，瀏覽器收到後，會直接換頁(回傳的 response 是 .html 檔案 )。

==下圖，輸入123，提交，301轉址。==
![](https://i.imgur.com/xTGq81l.png)

==下圖，出現google頁面，因為回傳的response，是google的html檔==

![](https://i.imgur.com/kK4ZkBz.png)

==下圖，response回傳回來的，google的html檔==

![](https://i.imgur.com/Hk61DoA.png)

___
## 傳送資料的第二種方式：ajax
第一種方式的缺點，會換頁。

第二種方式：可以不換頁，在頁面上，去改變部分內容。

ajax(Asynchronous JavaScript and XML)，只要是任何伺服器交換資料，是用 JavaScript，都可以叫 ajax。

原理: 

瀏覽器的 JS 透過瀏覽器發送request給Server回應response給瀏覽器，瀏覽器將結果給瀏覽器的 JS。

瀏覽器的 JS -> 瀏覽器(request) -> Server -> 瀏覽器(response) -> 瀏覽器的 JS 
![](https://i.imgur.com/Sa1FBtd.png)
```
解釋 : 

// XMLHttpRequest()，瀏覽器提供的物件，new可以去實作。

const request = new XMLHttpRequest()

//request.onload，放一個function，request現在是一個物件，實作後，會執行onload，這function。

//if，判斷是狀態是否成功，responseText，取得的內容。err，錯誤訊息。

request.onload = function () {
    if (request.status >= 200 && request.status <= 400) {
      console.log(request.responseText)
    } else {
       console.log(err)
    }
}

//如果有錯誤，會觸發的 function

request.onerror = function () {
  console.log('error')
}

//open，要發request的地方，可以填3個參數，method，url，bool。
//bool，true，非同步。false，同步。ajax一定是非同步。

request.open('GET', '', true)

//真的將request，發出。
request.send()
```
範例 : 可以開啟，檢查，Network，Response，查看資料。
```
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ajax - text</title>
  </head>
  <body>
    <div class="app"></div>
  </body>
  <script>
    const request = new XMLHttpRequest()
    request.onload = function () {
      if (request.status >= 200 && request.status <= 400) {
        console.log(request.responseText)
      } else {
        console.log(err)
      }
    }

    request.onerror = function () {
      console.log('error')
    }

    request.open('GET', 'https://reqres.in/api/users', true)
    request.send()
  </script>
</html>
```

___
## 詳細解析 XMLHttpRequest

重點 : 回傳回來的是字串，需要用[JSON.parse()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)，去轉成物件使用。

在解釋一次，ajax，的實作範例。可以將上章範例，的onload，用EventListene去做。

```
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ajax - text</title>
  </head>
  <body>
    <div class="app"></div>
  </body>
  <script>
    const request = new XMLHttpRequest()

    request.addEventListener('load', function () {
      if (request.status >= 200 && request.status <= 400) {
        console.log(request.responseText)
      } else {
        console.log(request.status)
      }
    })
    request.onerror = function () {
      console.log('error')
    }

    request.open('GET', 'https://reqres.in/api/users', true)
    request.send()
  </script>
</html>
```
___
## Same origin policy 與跨網域問題
好文推薦：https://blog.techbridge.cc/2017/05/20/api-ajax-cors-and-jsonp/

1.瀏覽器的政策 : Same origin policy(同源政策)，甚麼是同源，可以看成相同網域，就是同源，HTTP跟HTTPS，是分開的。

2.瀏覽器的政策 : 跨來源資源公用 ( CORS )，這東西就是為了解決，同源政策的問題，有時可能會去別人的Server，拿資料，所以需要有此功能，這時就有一個規範。

如果想要跨來源存取在header，加 Access-Control-Allow-Origin，哪些來源可以存取API的response，這來源是甚麼，在發 request，瀏覽器會在 request 的 header 裡，加上 Origin，就是你現在網域的 Domain，瀏覽器會自動加上來源，在server端，就可以決定是否給他存取的權限。

所以要解決這問題，除非是同來源，在同樣的 Domain，或是 server 加上 Access-Control-Allow-Origin。
___
## 瀏覽器住海邊嗎，為什麼管這麼寬？
為甚麼要有同源政策，跨來源資源公用，這兩個政策，所以到底要怎麼存取，跨網域的資源。

因為安全性，瀏覽器許多政策，都是因為安全性，為了不讓他人可以隨意存取他人電腦的資源。

以上瀏覽器政策，都是瀏覽器加的枷鎖，如果沒有瀏覽器，像是用 node，去發 request，並沒有那些枷鎖。
___
## 你絕對用過，但絕對沒想過的第三種方式
補充講解，現在已經很少再用了。[JSONP (JSON with Padding)](https://www.fooish.com/json/jsonp.html)

利用有些標籤不會受到同源政策的影響，例如`<img>`、`<script>`，使用`<script>`去拿到資料。

___
## 單向傳送資料的延伸應用（email 與追蹤Domain）
單向傳送資料，範例，如何知道發送的 email，被打開的次數追蹤，廣告信的次數追蹤。

例如，在 email中，放一個透明的`<img width="1" heigth="1" src="https://example.com/users_open/123">`圖，當使用者開信，就會發送一個 request，去計算次數。
___
## 綜合示範：抓取資料並顯示
1.先將版型刻好。
```
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ajax - text</title>
    <style>
      body {
        font-size: 38px;
      }
      .profile {
        border: 1px solid #333;
        margin-top: 20px;
        display: inline-flex;
        align-items: center;
      }
      .profile__name {
        margin: 0 10px;
      }
    </style>
  </head>
  <body>
    <div class="app">
      <div class="profile">
        <div class="profile__name">George Bluth</div>
        <img
          class="profile__img"
          src="https://reqres.in/img/faces/1-image.jpg"
        />
      </div>
    </div>
  </body>
  <script>
    const request = new XMLHttpRequest()

    request.addEventListener('load', function () {
      if (request.status >= 200 && request.status <= 400) {
        console.log(request.responseText)
      } else {
        console.log(request.status)
      }
    })
    request.onerror = function () {
      console.log('error')
    }

    request.open('GET', 'https://reqres.in/api/users', true)
    request.send()
  </script>
</html>
```
2.拿到資料

3.將固定的內容，換成是動態的內容。
```
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ajax - text</title>
    <style>
      body {
        font-size: 38px;
      }
      .profile {
        border: 1px solid #333;
        margin-top: 20px;
        display: inline-flex;
        align-items: center;
      }
      .profile__name {
        margin: 0 10px;
      }
    </style>
  </head>
  <body>
    <div class="app"></div>
  </body>
  <script>
    const request = new XMLHttpRequest()
    const container = document.querySelector('.app')

    request.addEventListener('load', function () {
      if (request.status >= 200 && request.status <= 400) {
        const response = request.responseText
        const json = JSON.parse(response)
        const users = json.data
        for (let i = 0; i < users.length; i++) {
          const div = document.createElement('div')
          div.classList.add('profile')
          div.innerHTML = `<div class="profile__name">${users[i].first_name} ${users[i].last_name}</div>
                <img class="profile__img" src="${users[i].avatar}"/>
                `
          container.appendChild(div)
        }
      } else {
        console.log(request.status)
      }
    })
    request.onerror = function () {
      console.log('error')
    }

    request.open('GET', 'https://reqres.in/api/users', true)
    request.send()
  </script>
</html>
```
___
## 小地方大學問：內容產生的地方在 client 還是 server
client side rendering : 用 js 動態去產生內容。

以上範例，去檢視網頁原始碼，看起來是沒有內容的。

缺點是，搜尋引擎，看到此網頁會認為，是沒有內容的，因為你的內容都是 js 動態產生的。
___
# 總結
## 課程總結
基本上在網頁上頁JavaScript時，所關注的面向，有3個 : 
1. 介面 : 如何改變
如何使用 JavaScript 去改變，用 html 跟 css ，所寫出來的 UI，例如改變元素，新增元素。

2. 事件 : 如何監聽事件並做出反應
比如，按一個按紐去新增元素，或是按按鈕改變背景顏色，如果會介面跟事件，能做出7-8成的東西。

3. 資料 : 如何跟伺服器交換資料
或是如何讓資料再瀏覽器保存下來。

> 基本上如果會以上3點，能寫出任何的程式，網頁的部分，不外乎就這3點。

___