---
# System prepended metadata

title: 筆記｜第五堂：開發思維與程式邏輯整合
tags: [學習筆記, JavaScript]

---

`#JavaScript` `#六角前端課程` `#學習筆記` `#骨力走傱`

## 什麼是 DOM？
文件物件模型（Document Object Model）。

> 是一個將 HTML 文件以樹狀的結構來表示的模型，而組合起來的樹狀圖，我們稱之為「DOM Tree」。

若將以下程式碼的關係以圖像呈現，會得到一張樹狀圖，**一個節點就是一個標籤，JavaScript 透過 DOM 提供的方法，可以操作這些節點，進而改變 HTML 的結構、樣式及內容。**

```html=
<html>

<head>
  <title>Shop</title>
</head>

<body>
  <h1>熱銷商品</h1>
  <p>歡迎選購</p>
</body>

</html>
```

![Untitled](https://hackmd.io/_uploads/SkNzAo1kWg.jpg)

### 資料來源
* [重新認識 JavaScript: Day 11 前端工程師的主戰場：瀏覽器裡的 JavaScript](https://ithelp.ithome.com.tw/articles/10191666)
* [JavaScript 從零開始 - Day25- 認識 DOM 文件物件模型](https://ithelp.ithome.com.tw/articles/10241371)

## 如何操作 DOM 節點？
### 選取節點
#### `querySelector()`
```html=
<p id="text" class="primary-color">藍色泡泡槍</p>
```
```css=
.primary-color {
    color: lightskyblue;
}
```
```javascript=
const tagNode = document.querySelector("p");
const tagId = document.querySelector("#text");
const classNode = document.querySelector(".primary-color");

console.log(tagNode); // <p class="primary-color">藍色泡泡槍</p>
console.log(tagId); // <p class="primary-color">藍色泡泡槍</p>
console.log(classNode); //<p class="primary-color">藍色泡泡槍</p>
```
* `document.querySelector(" HTML 標籤或 CSS 類別選擇器");`
* Document 是一個物件，因此可以使用點記法 `.` 存取或呼叫其屬性與方法。
* `querySelector` 會返回選取對象的 HTML 結構，可以將結果賦值在變數上，以利後續操作。
* `("")` 內放入選取對象的 HTML 標籤、id 或 CSS 類別選擇器。
    * id → 其前綴符號的 `#`
    * CSS  → 其前綴符號的 `.`

### 選取多個節點
#### `querySelectorAll()`
```html=
<a href="#">連結</a>
<a href="#">連結</a>
<a href="#">連結</a>
```
```javascript=
const links = document.querySelectorAll("a");

console.log(links); // NodeList [a, a, a]
```
* `document.querySelectorAll(" HTML 標籤或 CSS 類別選擇器");`
* `querySelectorAll` 會以陣列的形式回傳節點列表，因此選取多個節點後，要對其中一個節點操作時，需要使用索引值選取。
    * `links[0].textContent = "新增文字";`

### 新增、修改文字
#### `textContent`
```html=
<p>藍色泡泡槍</p>
```
```javascript=
const tagNode = document.querySelector("p");
tagNode.textContent = "黃色泡泡槍";

console.log(tagNode); // <p>黃色泡泡槍</p>
```
* `DOM 節點.textContent = "要寫入的文字";`
* 要先選取節點，才能使用 `textContent` 寫入文字。
* 寫入的文字會覆蓋原本的內容。
* `textContent` **無法將 HTML 標籤轉換為網頁結構**。若輸入 `tagNode.textContent = "<h1>黃色泡泡槍</h1>";`，也只會渲染 `<h1>黃色泡泡槍</h1>` 這段文字在網頁上而已。若希望標籤能轉換為網頁結構，需要使用 `innerHTML` 這個方法。

### 新增、修改 HTML 標籤
#### `innerHTML`
```html=
<div>
    <p>文字</p>
</div>
```
```javascript=
const textNode = document.querySelector("div");
textNode.innerHTML = `<h1>標題</h1>`;

console.log(textNode); // <div><h1>標題</h1></div>
```
* `DOM 節點.innerHTML = "要修改的 HTML 結構";`
* `innerHTML` 會先清除節點的內容，再新增 HTML 結構，故我要在 `<p>` 標籤的位置新增 `<h1>` 標籤時，需要先選取 `<p>` 標籤的父層 `<div>`，再使用 `innerHTML` 新增結構。
* 可以使用**變數**替換內容：
    ```javascript=
    const textNode = document.querySelector("div");
    let text = "虱目魚沒夢不應該";
    textNode.innerHTML = `<h1>${text}</h1>`;

    console.log(textNode); // <div><h1>虱目魚沒夢不應該</h1></div>
    ```

### 新增子節點
#### `appendChild()`
```javascript=
// 取選要新增子節點的父容器
const appendChild = document.querySelector(".append-child");

// 使用 appendChild 前，要先使用 document.createElement() 建立新元素
const li = document.createElement("li");
li.textContent = "ㄉㄊㄋ";

// 可以將指定的 childNode 節點，加入到 Node 父層節點的末端
appendChild.appendChild(li);
```
* `document.createElement();` 新增新元素。
* `DOM 節點.appendChild(要加入的 HTML 結構);`
* `appendChild` 不會清除原本的 HTML 結構，而是在父層節點容器的末端，新增子元素。

#### 資料來源
* [重新認識 JavaScript: Day 13 DOM Node 的建立、刪除與修改](https://ithelp.ithome.com.tw/articles/10191867)

### 新增、修改 HTML 屬性
#### `setAttribute()`
```html=
<a href="#">連結</a>
```
```javascript=
const link = document.querySelector("a");
link.setAttribute("href","https://www.google.com/");
link.setAttribute("class","primary-color");

console.log(link); // <a href="https://www.google.com/" class="primary-color">連結</a>
```
* `DOM 節點.setAttribute("要修改的屬性名稱","要修改的內容");`
* 若要新增 `class` 屬性，`""` 內輸入的類別名稱，不用加前綴符號 `.`。

### 取出 HTML 結構、屬性及文字的方式
```html=
<div>
    <a href="https://www.google.com/" class="primary-color">google ㄉ 超連結</a>
</div>
```

```javascript=
const divNode = document.querySelector("div");
const link = document.querySelector("a");

// 取出 HTML 結構
console.log(divNode.innerHTML); // <a href="https://www.google.com/" class="primary-color">google ㄉ 超連結</a>

// 取出 HTML 屬性
console.log(link.getAttribute("href")); // https://www.google.com/
console.log(link.getAttribute("class")); // primary-color

// 取出標籤內的文字
console.log(link.textContent); // google ㄉ 超連結
```
* `DOM 節點.getAttribute("要取出其內容的屬性名稱");`
* 以上述的程式碼為例，若要取得 `<a>` 標籤內的網址，可以使用 `getAttribute` ，接著 `("")` 輸入 href，就會回傳內容。
* `divNode.innerHTML`、`link.textContent` 則是會回傳賦值在變數上的資料，進而得到 HTML 結構及文字。
* 取值的方法也可以應用在取出表單內容。
    * ```html=
        <input type="text" value="這是一個表格">
        ```
        
        ```javascript=
        const inputNode = document.querySelector("input");

        console.log(inputNode.value); // 這是一個表格
        ```
        
## 什麼是 Event？
事件（Events），使用者在瀏覽網頁時，會觸發許多事件，例如點擊按鈕、滾動畫面及點選表單等等，JavaScript 會透過監聽（Listen）等等的方法處理這些事件。

### 資料來源
* [JavaScript DOM Event（事件處理）](https://www.fooish.com/javascript/dom/event.html)

## 如何進行事件監聽？
### 註冊
#### `addeventListener()`
```html=
<input type="button" value="按鈕">
<h1>標題</h1>
```
```javascript=
const btn = document.querySelector("input");
const text = document.querySelector("h1");

btn.addEventListener("click",function(e){
    text.textContent = "按鈕已被點擊";
});
```
* `DOM 節點.addEventListener("監聽事件","事件觸發時要執行的函式");`
* `.` 前方為被監聽的 DOM 節點。
* `"click"` 點擊，表示監聽的事件種類，當 DOM 節點**被點擊時**，會執行後方的函式。
* [事件的種類](https://ithelp.ithome.com.tw/articles/10192175)

#### 是否有註冊成功？
![螢幕擷取畫面 (1667)](https://hackmd.io/_uploads/SyA7DBM1-x.png)
* 透過開發者工具的「事件監聽器」確認事件是否有註冊成功？以及註冊了哪些事件？

#### 迷你加法器練習
```html=
<input type="button" value="按鈕">
<h1>0</h1>
```
```javascript=
const btn = document.querySelector("input");
const text = document.querySelector("h1");
let counts = 0; // 紀錄數字

btn.addEventListener("click",function(e){
    counts++;
    text.textContent = counts; // 變數 counts 的值會覆蓋 <h1> 的內容
});
```
### 參數 `e`
#### `function(e)`
![螢幕擷取畫面 (1669)](https://hackmd.io/_uploads/ryySCHMJbl.png)
* `function(e)` 中的 `e` 為事件 `event` 的縮寫。若使用 `console.log` 查看，會回傳當前被觸發節點的**事件物件快照**，可利用這份資料進一步操作或應用。

### 取得節點的 HTML 標籤名稱
#### `nodeName`
```html=
<input type="button" value="按鈕">
```
```javascript=
const btn = document.querySelector("input");

btn.addEventListener("click",function(e){
    console.log(btn.nodeName); // INPUT
});
```
* `DOM 節點.nodeName`
* 回傳節點的 HTML 標籤名稱。

### 取得當前被觸發的元素位置
#### `e.target`
```html=
<ul>
    <li>文字</li>
    <li>文字</li>
    <li><input type="button" value="按鈕"></li>
</ul>
```
```javascript=
const ul = document.querySelector("ul");
const btn = document.querySelector("input");

ul.addEventListener("click",function(e){
    console.log(e.target); // <li> ... </li>
});

btn.addEventListener("click",function(e){
    console.log(e.target); // <input type="button" value="按鈕">
});
```
* `target` 是 DOM 的屬性，能告知開發者當前是哪一個元素被觸發了。
* 會根據註冊事件監聽的範圍大小，回傳 `<ul>` 或一個按鈕的結構。

### 與 nodeName 的應用
```html=
<ul>
    <li>文字</li>
    <li>文字</li>
    <li><input type="button" value="按鈕"></li>
</ul>
```
```javascript=
const ul = document.querySelector("ul");
const btn = document.querySelector("input");

// 取得 btn 的 nodeName
// btn.addEventListener("click",function(e){
//     console.log(e.target.nodeName); // INPUT 
// });

ul.addEventListener("click",function(e){
    if(e.target.nodeName === "INPUT") {
        console.log("您點擊到按鈕了！");
    } else {
        console.log("您點擊到其他地方了！");
    }
});
```
1. 希望使用者點擊到按鈕時，回傳「您點擊到按鈕了！」的資訊。
2. 在 `btn` 的 `e.target` 中找到 `nodeName`，取得節點名稱 `INPUT`。
3. 對 `ul` 註冊事件監聽，若點擊到按鈕（即 `e.target.nodeName === "INPUT"`），就回傳「您點擊到按鈕了！」的資訊；反之，則回傳「您點擊到其他地方了！」。

### 取消預設的觸發行為
#### `preventDefault()`
```html=
<a href="https://www.google.com/">連結</a>
```
```javascript=
const textNode = document.querySelector("a");

textNode.addEventListener("click",function(e) {
    e.preventDefault();
});
```
* 未加入 `e.preventDefault();` 之前，點擊 `<a>` 標籤時，網頁會發生轉址；若想要取消預設的觸發行為，可以使用 `e.preventDefault();` 取消。

## 陣列處理方法
### 前言
面對大量資料時，若要一筆一筆計算加總，是效率極低的事，因此需要學習陣列處理的方法，如 `map`、`filter`、`forEach`，能快速完成資料篩選、轉換與彙整，提升作業效率。

這裡先筆記 `forEach()` 的使用方法。

### forEach()
```javascript=
let arr = [1, 2, 3];

arr.forEach(function(item,index,array){
    console.log(item,index,array);
});

// 1 0 [1, 2, 3]
// 2 1 [1, 2, 3]
// 3 2 [1, 2, 3]
```
* `變數.forEach(function(item,index,array){...};`
* 陣列裡有幾筆資料，函式區塊內的程式碼就會執行幾次。
* 可自定義函式中的參數名稱，但參數**位置**所代表的意義是固定的：
    ```javascript=
    let arr = [1];
     
    arr.forEach(function(num,i,arr){
    console.log(num,i,arr);
    });

    // 1 0 [1]
    ```
    * 參數 1：目前正在處理的陣列元素。
    * 參數 2：該元素的索引值。
    * 參數 3：整個陣列本身。
* 可依需求決定使用哪些參數，例如不會用的 `array` 的話，可以省略。

## forEach 的應用
### 未使用 `forEach()` 前
```javascript=
// 加總陣列中的元素值。

let arr = [10, 20, 30];
let total = 0;

total = arr[0] + arr[1] + arr[2];

console.log(total); // 60
```

### 使用 `forEach()` 後
```javascript=
// 加總陣列中的元素值。

let arr = [10, 20, 30];
let total = 0;

arr.forEach(function(item,index){
    total += item;
    console.log(total);
});

// 執行第一次
// let total = 0;
// total += item; ➡️ 0 + 10 = 10
// total = 10;

// 執行第二次
// total = 10;
// total += item; ➡️ 10 + 20 = 30
// total = 30;

// 執行第三次
// total = 30;
// total += item; ➡️ 30 + 30 = 60
// total = 60;

console.log(total); // 60
```

#### 若將 `let total = 0;` 放在函式區塊內
```javascript=
// 加總陣列中的元素值。

let arr = [10, 20, 30];

arr.forEach(function(item,index){
    let total = 0;
    total += item;
    console.log(total);
});

// 函式內發生了什麼事？
// 執行第一次
// let total = 0;
// total += item; ➡️ 0 + 10 = 10

// 執行第二次
// let total = 0;
// total += item; ➡️ 0 + 20 = 20
// total = 20;

// 執行第三次
// let total = 0;
// total += item; ➡️ 0 + 30 = 30
// total = 30;

console.log(total); // total is not defined
```
* 函式區塊內的變數只存在於區塊中，故每次都會執行 `let total = 0;` 時，都無法留下加總紀錄。
* 外層的 `console.log(total);` 無法往內層找到變數 `total`，故回傳 `total is not defined`。

### 與 `if` 的應用
```javascript=
// 計算有陣列中有幾筆偶數，並紀錄結果。
// 偶數的條件 -> 餘數是 0

let data = [128415, 1893188, 10, 187391, 15400];
let evenCount = 0;
let res = [];

data.forEach(function(item){
    if(item % 2 === 0){
        evenCount ++;
        res.push(item);
    }
});

console.log(evenCount); // 3
console.log(res); // [1893188, 10, 15400]
```

### forEach 可以讀取物件資料
```javascript=
// 記錄學生的名字

let classMate = [
    {
        name: "Tom",
        age: 10
    },
    {
        name: "Eva",
        age: 11
    },
    {
        name: "Lily",
        age: 10
    }
];

let nameList = [];

classMate.forEach(function(item){
    console.log(item.name);
    nameList.push(item.name);
});

console.log(nameList); // ['Tom', 'Eva', 'Lily']
```
* `classMate.forEach(function(item){console.log(item);});` 回傳：
    * `{name: 'Tom', age: 10}`
    * `{name: 'Eva', age: 11}`
    * `{name: 'Lily', age: 10}`
    * 是物件資料，故使用點記法讀取 `name` 的值。
* 可以先使用 `console.log` 查看當前的資料是陣列還是物件？接著思考後續的讀取方式，以及可以使用的處理方法。

## DOM 與 forEach 的整合
### 整合 innerHTML 資料
```javascript=
let data = [
  {
    Charge:"免費",
    name:"廖洧杰充電站"
  }, {
    Charge: "投幣式",
    name: "小花充電站"
  }, {
    Charge: "投幣式",
    name: "小明充電站"
  }, {
    Charge: "投幣式",
    name: "小天充電站"
  }
];

// 1. 整合 innerHTML 資料
// 1-1. 選取 DOM 節點
const list = document.querySelector("ul");

// 1-2. 組合結構用
let str = "";

// 1-3. 使用 forEach 逐筆讀取資料、字串相加，並賦值在 str
data.forEach(function(item) {
    let content = `<li>${item.name}．${item.Charge}</li>`
    str += content;
});

// 1-4. 使用 innerHTML 將組好的資料渲染至網頁
list.innerHTML = str;
```

#### `forEach` 的執行過程
```javascript=
data.forEach(function(item) {
    let content = `<li>${item.name}．${item.Charge}</li>`
    str += content;
});

// 執行過程
// 第一次
// <li>廖洧杰充電站．免費</li>

// 第二次
// <li>廖洧杰充電站．免費</li><li>小花充電站．投幣式</li>

// 第三次
// <li>廖洧杰充電站．免費</li><li>小花充電站．投幣式</li><li>小明充電站．投幣式</li>

// 第四次
// <li>廖洧杰充電站．免費</li><li>小花充電站．投幣式</li><li>小明充電站．投幣式</li><li>小天充電站．投幣式</li>
```

### 設計初始化功能
```javascript=
// 2. 設計初始化功能
// 前一段程式碼的作用是將資料渲染到網頁上
// 可以將這整個流程封裝成一個函式
// 作為「資料初始化」的功能

function init() {
  const list = document.querySelector("ul");
  let str = "";

  data.forEach(function (item) {
    let content = `<li>${item.name}．${item.Charge}</li>`;
    str += content;
  });

  list.innerHTML = str;
};

init();
```

### 註冊事件監聽與篩選資料
```javascript=
// 3. 註冊事件監聽與篩選資料
// 3-1. 選取監聽範圍
const stationFilter = document.querySelector(".stationFilter");

// 3-2. 註冊監聽
stationFilter.addEventListener("click", function (e) {
  // 3-3 取出 value
  // console.log(e.target.value); // 根據點擊範圍會回傳免費、投幣式或 undefined

  // 3-4. if 流程
  // 3-4-1. 先判斷點擊到哪個按鈕
  if (e.target.value === undefined) {

    console.log("請點擊按鈕。");

  } else {

    let str = "";

    // 3-4-2. 篩選資料，若 e.target.value === item.Charge 就執行區塊內程式碼
    data.forEach(function (item) {
      if (e.target.value === item.Charge) {
        str += `<li>${item.name}．${item.Charge}</li>`;
      }
    });

    // 3-5. 渲染資料
    const list = document.querySelector("ul");
    list.innerHTML = str;
  }
});
```

### 使用全域變數
```javascript=
const list = document.querySelector("ul");
const stationFilter = document.querySelector(".stationFilter");

function init() {
  let str = "";
  data.forEach(function (item) {
    let content = `<li>${item.name}．${item.Charge}</li>`;
    str += content;
  });

  list.innerHTML = str;
}

init();

stationFilter.addEventListener("click", function (e) {
  if (e.target.value === undefined) {
    console.log("請點擊按鈕。");
  } else {
    let str = "";
    data.forEach(function (item) {
      if (e.target.value === item.Charge) {
        str += `<li>${item.name}．${item.Charge}</li>`;
      }
    });

    list.innerHTML = str;
  }
});
```
* 在兩組函式中，都有出現 `const list = document.querySelector("ul");`，此時可以使用「**若函式內層找不到變數時，會往外層找。**」的特性，將選取 DOM 節點的程式碼，拉到全域管理。

### 新增篩選條件
```javascript=
// 5. 新增「全部」按鈕的篩選條件
data.forEach(function (item) {
  if (e.target.value === "全部") {
    str += `<li>${item.name}．${item.Charge}</li>`;
  } else if (e.target.value === item.Charge) {
    str += `<li>${item.name}．${item.Charge}</li>`;
  }
});
```

### 新增資料
```javascript=
// 6. 新增資料
// 6-1. 選取 DOM 節點
const stationName = document.querySelector(".stationName");
const stationCharge = document.querySelector(".stationCharge");
const btn = document.querySelector(".btn");

// 6-2. 註冊監聽
btn.addEventListener("click",function(e){
    // 6-2-1. 取出表格裡的 value 後組合資料
    let obj = {};
    obj.name = stationName.value;
    obj.Charge = stationCharge.value;
    data.push(obj);
    // 6-2-2. 重新將資料渲染至網頁
    init();
    // 6-2-3. 清除輸入過的文字與指定選項預設文字
    stationName.value = "";
    stationCharge.value = "免費";
});
```

### 總結
#### HTML
```html=
<h2>新增資料</h2>
<input type="text" class="stationName" placeholder="充電站名稱">
<select class="stationCharge">
    <option value="免費">免費</option>
    <option value="投幣式">投幣式</option>
</select>
<input type="button" class="btn" value="儲存">

<h2>顯示資料</h2>
<div class="stationFilter">
    <input type="button" value="全部">
    <input type="button" value="免費">
    <input type="button" value="投幣式">
</div>

<ul>
</ul>
```

#### JavaScript
```javascript=
// 資料
let data = [
  {
    Charge: "免費",
    name: "廖洧杰充電站",
  },
  {
    Charge: "投幣式",
    name: "小花充電站",
  },
  {
    Charge: "投幣式",
    name: "小明充電站",
  },
  {
    Charge: "投幣式",
    name: "小天充電站",
  },
];

// 選取 DOM
const list = document.querySelector("ul");
const stationFilter = document.querySelector(".stationFilter");
const stationName = document.querySelector(".stationName");
const stationCharge = document.querySelector(".stationCharge");
const btn = document.querySelector(".btn");

// 初始化
function init() {
  let str = "";
  data.forEach(function (item) {
    let content = `<li>${item.name}．${item.Charge}</li>`;
    str += content;
  });

  list.innerHTML = str;
}

init();

// 篩選功能
stationFilter.addEventListener("click", function (e) {
  if (e.target.value === undefined) {
    console.log("請點擊按鈕。");
  } else {
    let str = "";
    data.forEach(function (item) {
      if (e.target.value === "全部") {
        str += `<li>${item.name}．${item.Charge}</li>`;
      } else if (e.target.value === item.Charge) {
        str += `<li>${item.name}．${item.Charge}</li>`;
      }
    });
    list.innerHTML = str;
  }
});

// 新增功能
btn.addEventListener("click",function(e){
    let obj = {};
    obj.name = stationName.value;
    obj.Charge = stationCharge.value;
    data.push(obj);
    init();
    stationName.value = "";
    stationCharge.value = "免費";
});
```