---
# System prepended metadata

title: 【選修】作業延伸練習
tags: [學期 2-1]

---

###### tags: `學期 2-1`

# 【選修】作業延伸練習


## 新單元上架:【選修】作業延伸練習

Hi {{user_nickname}}，

第三週的學習狀況還好嗎～是不是覺得 DOM 又是另一個大魔王呀 XD

在 DOM 的學習中，很常因為抽象的概念，讓同學完成作業後會有「結果是正確的沒錯，但我不太知道其中發生了什麼事情？」的困擾。

AC 團隊特別搜集了幾個，同學們在「寫完作業後」還是會有的疑惑，推薦**已經完成本週作業的同學**，可以挑戰回答延伸練習的問題！但若還在進度學習的同學，還是先以主課程內容優先喔，之後有餘裕再回來補上【選修】的內容就好！


#### 新單元上架，完成 Week 3 作業後，可以試試回答延伸練習的問題喔！

觀看新單元 👉 [【選修】作業延伸練習](https://lighthouse.alphacamp.co/courses/98/units/28243)



<div style="width:100%"><img style="max-width:1000px; width:100%;" src="https://assets-lighthouse.alphacamp.co/uploads/image/file/21381/banner.001.png"></div>

## week 1
>**學習成果與目標**
>・加強呼叫函式、組合函式練習
>・提升程式邏輯裡解練習

恭喜你完成 Week 1 作業來到這個單元，有沒有對函式的使用更加上手了呢？

函式的應用非常廣泛多元，在開始學習時，需要花一點時間梳理呼叫函式的邏輯，除了作業以外，我們也準備了幾個綜合練習題，讓你練習看看，自己是否有真的了解背後的觀念。

一起來練習回答以下題目吧！

#### Exercise 1：用加減乘法練習組合函式、呼叫函式

在起手式程式碼中，有加法、減法、乘法三個已被宣告的函式
<pre class="prettyprint">
function addition (x, y) {
  return x + y
}

function subtraction (x , y) {
  return x - y
}

function multiplication (x, y) {
  let result = 0;
  for (let i = 0; i < y; i++) {
    result = addition(result, x)
  } 
  return result
}

// write your code here
</pre>

**Q：請運用這三個函式，計算出下列數學式**
1. 3 + (5 - 1)
2. 5 - (3 * 4)
3. (4 - 2) * (1 + 3)
4. ((5 - 7) + 3) * 2

提示：可以使用 console.log 印出計算結果，以確認計算結果是否正確


<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise1" aria-expanded="false" aria-controls="exercise1">參考答案</a>

<div class="collapse" id="exercise1">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    <pre class="prettyprint">
    function addition (x, y) {
  return x + y
}

function subtraction (x , y) {
  return x - y
}

function multiplication (x, y) {
  let result = 0;
  for (let i = 0; i < y; i++) {
    result = addition(result, x)
  } 
  return result
}

// write your code here

// 3 + (5 - 1) = 7
console.log(addition(3, subtraction(5, 1)))

// 5 - (3 * 4) = -7
console.log(subtraction(5,multiplication(3, 4)))

// (4 - 2) * (1 + 3) = 8
console.log(multiplication(subtraction(4, 2), addition(1, 3)))


// ((5 - 7) + 3) * 2 = 2
console.log(multiplication(addition(subtraction(5,7), 3) ,2))
    </pre>

  </div>
</div>


#### Exercise 2：理解雙迴圈的邏輯
在前面的作業，我們有推薦大家可以使用推薦一個視覺化工具：http://pythontutor.com/javascript.html 來輔助自己釐清程式邏輯，下面的練習就是來讓同學刻意去意識迴圈的邏輯。

**Q：先閱讀起手式程式碼，並試著理解雙迴圈邏輯。請試著在起手式程式碼中加入 console.log 讓程式印出相同的指定訊息。**

#### 起手式程式碼
<pre class="prettyprint">
const players = [
  { name: 'Ellen', email: 'ellen@example.com', ticket: 'BB1750' },
  { name: 'Walter', email: 'walter@example.com', ticket: 'EI5724' },
  { name: 'Walter', email: 'walter@example.com', ticket: 'EI5724' },
  { name: 'Tim', email: 'tim@example.com', ticket: 'CK4592' },
  { name: 'Kevin', email: 'kevin@example.com', ticket: 'TT1804' }
]
const blackList = [
  { name: 'Tim', email: 'tim@example.com', ticket: 'CK4592' },
  { name: 'Walter', email: 'walter@example.com', ticket: 'EI5724' }
]

for (let i = players.length - 1; i >= 0; i--) {
  for (let j = 0; j < blackList.length; j++) {
    if (players[i].email === blackList[j].email) {
      players.splice(i, 1)
    }
  }
}

console.log(players)

</pre>



#### 指定訊息
<pre class="prettyprint">
===== i 是 4 的外層迴圈開始 =====
----- j 是 0 的內層迴圈開始 -----
players[i].email 是 kevin@example.com，blackList[j].email 是 tim@example.com
----- j 是 0 的內層迴圈結束 -----
----- j 是 1 的內層迴圈開始 -----
players[i].email 是 kevin@example.com，blackList[j].email 是 walter@example.com
----- j 是 1 的內層迴圈結束 -----
===== i 是 4 的外層迴圈結束 =====

===== i 是 3 的外層迴圈開始 =====
----- j 是 0 的內層迴圈開始 -----
players[i].email 是 tim@example.com，blackList[j].email 是 tim@example.com
發現黑名單，故刪除。刪除後的 players[i].email 是 kevin@example.com
----- j 是 0 的內層迴圈結束 -----
----- j 是 1 的內層迴圈開始 -----
players[i].email 是 kevin@example.com，blackList[j].email 是 walter@example.com
----- j 是 1 的內層迴圈結束 -----
===== i 是 3 的外層迴圈結束 =====

===== i 是 2 的外層迴圈開始 =====
----- j 是 0 的內層迴圈開始 -----
players[i].email 是 walter@example.com，blackList[j].email 是 tim@example.com
----- j 是 0 的內層迴圈結束 -----
----- j 是 1 的內層迴圈開始 -----
players[i].email 是 walter@example.com，blackList[j].email 是 walter@example.com
發現黑名單，故刪除。刪除後的 players[i].email 是 kevin@example.com
----- j 是 1 的內層迴圈結束 -----
===== i 是 2 的外層迴圈結束 =====

===== i 是 1 的外層迴圈開始 =====
----- j 是 0 的內層迴圈開始 -----
players[i].email 是 walter@example.com，blackList[j].email 是 tim@example.com
----- j 是 0 的內層迴圈結束 -----
----- j 是 1 的內層迴圈開始 -----
players[i].email 是 walter@example.com，blackList[j].email 是 walter@example.com
發現黑名單，故刪除。刪除後的 players[i].email 是 kevin@example.com
----- j 是 1 的內層迴圈結束 -----
===== i 是 1 的外層迴圈結束 =====

===== i 是 0 的外層迴圈開始 =====
----- j 是 0 的內層迴圈開始 -----
players[i].email 是 ellen@example.com，blackList[j].email 是 tim@example.com
----- j 是 0 的內層迴圈結束 -----
----- j 是 1 的內層迴圈開始 -----
players[i].email 是 ellen@example.com，blackList[j].email 是 walter@example.com
----- j 是 1 的內層迴圈結束 -----
===== i 是 0 的外層迴圈結束 =====
</pre>
<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise2" aria-expanded="false" aria-controls="exercise2">參考答案</a>

<div class="collapse" id="exercise2">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    <pre class = "prettyprint">
    for (let i = players.length - 1; i >= 0; i--) {
  console.log(`===== i 是 ${i} 的外層迴圈開始 =====`)
  
  for (let j = 0; j < blackList.length; j++) {
    console.log(`----- j 是 ${j} 的內層迴圈開始 -----`)
    console.log(`players[i].email 是 ${players[i].email}，blackList[j].email 是 ${blackList[j].email}`)
    if (players[i].email === blackList[j].email) {
      players.splice(i, 1)
      console.log(`發現黑名單，故刪除。刪除後的 players[i].email 是 ${players[i].email}`)
    }
    console.log(`----- j 是 ${j} 的內層迴圈結束 -----`)
  }
  
  console.log(`===== i 是 ${i} 的外層迴圈結束 =====\n`)
}
    </pre>

  </div>
</div>


#### Exercise 3：隨機產生數字 Math.random() 的用法
在第一週的作業中，我們運用 Math.random() 的函式來進行隨機抽獎，為了要抽出符合題目產生號碼，我們會用 Math.floor() 來搭配 Math.random()。

然而，不論是用 Math.floor() 還是 Math.ceil() 都可以產生我們所需的數值區間，以隨機產生 0~9 的整數舉例：
<pre class = "pretty print">
// 以 Math.floor() 來寫
0 ≤ Math.random() < 1
0 ≤ Math.random() * 10 < 10
0 ≤ Math.floor(Math.random() * 10) ≤ 9

// 以 Math.ceil() 來寫
0 ≤ Math.random() < 1
0 ≤ Math.random() * 9 < 9
0 ≤ Math.ceil(Math.random() * 9) ≤ 9
</pre>

**Q：既然兩種寫法都可以產出 0~9 的整數，為什麼不使用 Math.ceil() 來產生數字呢？請試著用文字寫下你的想法。**

提示：用 MDN 複習三個函式的用法，並試想產生每個數字的情境吧！
- [Math.random()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random)
- [Math.floor()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor)
- [Math.ceil()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil)

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise3" aria-expanded="false" aria-controls="exercise3">參考答案</a>

<div class="collapse" id="exercise3">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
   以隨機產出 0~9 的整數為例，因為兩者產生出 0 的機率不同
<br>Math.ceil(Math.random() * 9) 只有在 Math.random() 剛好回傳 0 的時候，才會回傳 0，所以產生出 0 的機率非常非常非常小
<br>可以參考討論串 Random Number, Math.floor(...) vs Math.ceil(...)：
<p><a href="https://stackoverflow.com/questions/15830658/random-number-math-floor-vs-math-ceil" target="_blank">https://stackoverflow.com/questions/15830658/random-number-math-floor-vs-math-ceil</a> </p>

  </div>
</div>


## week 2
>**學習成果與目標**
>・理解如何活用 HTML/CSS 呈現相同畫面
>・能觀察出 HTML 的標籤屬性，並能判斷如何設定屬性達到自己的需求

恭喜你完成 Week 2 作業來到這個單元，通過作業你是否更加掌握 HTML/CSS 的應用了呢？

了解 HTML/CSS 的特性能夠幫助我們更自在地活用它們，讓我們通過以下的題目一起來探索吧！

#### Exercise 1: 元素層級堆疊
我們在 [U:認識 Position](https://lighthouse.alphacamp.co/courses/98/units/26010) 學會運用 z-index 來賦予圖層「深度」，而 HTML/CSS 中很多呈現都有不少異曲同工之妙的作法。
##### Q1: 試試看複製起手式程式碼，你是否能「只更改 CSS，不動任何 HTML」調整這些色塊的堆疊方式呢？

<div style="width:100%"> <a href="https://assets-lighthouse.alphacamp.co/uploads/image/file/21826/F2-1________w2Q1.png" target="_blank"><img style="max-width:700px;width:100%;" src="https://assets-lighthouse.alphacamp.co/uploads/image/file/21826/F2-1________w2Q1.png"></a></div>



##### HTML 起手式程式碼
<pre class="prettyprint">
    &lt;div class="parent">
      &lt;div class="red">&lt;/div>
      &lt;div class="green">&lt;/div>
      &lt;div class="blue">&lt;/div>
    &lt;/div>
</pre>

##### CSS 起手式程式碼
<pre class="prettyprint">
div {
  height: 100px;
  width: 100px;
}

.parent {
  position: relative;
}

.red {
  position: absolute;
  top: 30px;
  left: 30px;
  background-color: red;
  z
}

.green {
  position: absolute;
  top: 20px;
  left: 20px;
  background-color: green;
}

.blue {
  position: absolute;
  top: 10px;
  left: 10px;
  background:  blue;
}
</pre>

<small>你是如何思考的呢？將你的思考過程分享到留言區與同學交流吧！</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#w2-exercise1" aria-expanded="false" aria-controls="w2-exercise1">參考答案</a>

<div class="collapse" id="w2-exercise1">
只要改變 HTML 標籤順序，就可以讓元素依照設計稿的方式排列喔！
<div class ="prettyprint">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    <p>&lt;div class="parent"></p>
      <p>&lt;div class="blue">&lt;/div></p>
      <p>&lt;div class="green">&lt;/div></p>
     <p> &lt;div class="red">&lt;/div></p>
   <p> &lt;/div>

  </div>
</div>
</div>



#### Exercise 2: 瞭解預設屬性的概念


##### Q2: 將起手式程式碼放入自己的 codepen 中，將 CSS 中的註解解除，觀察頁面的變化，思考看看為什麼會有這種變化呢？

#### HTML 起手式程式碼
<pre class ="prettyprint">
    &lt;div>
      &lt;h1>我是 h1 標題&lt;/h1>
    &lt;/div>
</pre>

#### CSS 起手式程式碼
<pre class ="prettyprint">
div {
  border: 1px solid red;
}

h1 {
  border: 1px solid blue;
/*   margin: 0; */
}
</pre>

<small>你是如何思考的呢？將你的思考過程分享到留言區與同學交流吧！</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#w2-exercise2" aria-expanded="false" aria-controls="w2-exercise2">參考答案</a>

<div class="collapse" id="w2-exercise2">
因為 h1 標籤有預設的 margin 屬性，可以透過 margin: 0; 設定，將預設屬性覆蓋掉。

關於標籤的預設樣式，可以參考 
<p><a href="https://www.w3schools.com/cssref/css_default_values.asp" target="_blank">CSS Default Values Reference</a> </p>
</div>







## week 3

>**學習成果與目標**
>・針對同學在作業中常見的問題延伸練習


恭喜你完成 Week 3 作業來到這個單元，想必過程中也遇到了不少挑戰，首先鼓勵自己又克服了一個關卡，又學會新東西了！

在 DOM 的學習中，很常因為抽象的概念，讓同學完成作業後會有「結果是正確的沒錯，但我不太知道其中發生了什麼事情？」的困擾，此時，除了繳交作業同時在 Description 區塊提問以外；我們也準備了幾個常見的問題，讓你練習看看，自己是否有真正了解背後的觀念。

一起來練習回答以下題目吧！

#### Exercise 1：了解 innerHTML、innerText、textContent 的不同

在 [A15: 找出得分王](https://lighthouse.alphacamp.co/courses/98/assignments/2975)中，使用 innerHTML、innerText、textContent 都可以取出得分欄的分數，完成題目要求的功能，例如：

**用 innerHTML**
<pre class="prettyprint">
const playerRecords = document.querySelectorAll('.table tbody tr')
const thumbsUpIconHTML = '&lt;i class="fa fa-thumbs-up">&lt;/i>'

for (let i = 0; i < playerRecords.length; i++) {
  const score = parseInt(playerRecords[i].children[1].innerHTML, 10)
  if (score >= 20) {
    playerRecords[i].children[0].innerHTML += thumbsUpIconHTML
  }
}
</pre>

**用 innerText**
<pre class="prettyprint">
const playerRecords = document.querySelectorAll('.table tbody tr')
const thumbsUpIconHTML = '&lt;i class="fa fa-thumbs-up">&lt;/i>'

for (let i = 0; i < playerRecords.length; i++) {
  const score = parseInt(playerRecords[i].children[1].innerText, 10)
  if (score >= 20) {
    playerRecords[i].children[0].innerHTML += thumbsUpIconHTML
  }
}
</pre>

**用 textContent**
<pre class="prettyprint">
const playerRecords = document.querySelectorAll('.table tbody tr')
const thumbsUpIconHTML = '&lt;i class="fa fa-thumbs-up">&lt;/i>'

for (let i = 0; i < playerRecords.length; i++) {
  const score = parseInt(playerRecords[i].children[1].textContent, 10)
  if (score >= 20) {
    playerRecords[i].children[0].innerHTML += thumbsUpIconHTML
  }
}
</pre>

**Q：到底用哪個比較好，原因是什麼？**
<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise1" aria-expanded="false" aria-controls="exercise1">參考答案</a>

<div class="collapse" id="exercise1">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    目前的使用情境，使用 textContent 比較好。
    <p>詳細請參考下一個單元【選修】作業延伸練習：參考答案說明</p>

  </div>
</div>

#### Exercise 2：了解使用 document.createElement 可能會踩到的坑，以及對「如何提升程式碼的效能」有初步的認識

在 [A15: 找出得分王](https://lighthouse.alphacamp.co/courses/98/assignments/2975)中，如果是使用 document.createElement 創造出讚的 icon，像這樣：

<pre class="prettyprint">
const thumbsUpIcon = document.createElement('i')
thumbsUpIcon.classList.add('fa', 'fa-thumbs-up')
</pre>

**Q：這段程式碼應該要擺在哪個地方（A、B or C）：**
<pre class="prettyprint">
const playerRecords = document.querySelectorAll('.table tbody tr')
// A
for (let i = 0; i < playerRecords.length; i++) {
  const score = parseInt(playerRecords[i].children[1].textContent, 10)
  // B
  if (score >= 20) {
    // C
    playerRecords[i].children[0].appendChild(thumbsUpIcon)
  }
}
</pre>
<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise2" aria-expanded="false" aria-controls="exercise2">參考答案</a>

<div class="collapse" id="exercise2">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    擺在 B 和 C 都可以達成「在得分數 ≥ 20 的球員名字旁加上讚的符號」的目標，而擺在 C 的效能會比較好。
    <p>詳細請參考下一個單元【選修】作業延伸練習：參考答案說明</p>
  </div>
</div>


#### Exercise 3：迭代的對象是陣列中的元素時，建議不要使用的迴圈方法

在 [A17: 彩子的計分版](https://lighthouse.alphacamp.co/courses/98/assignments/2977)中，如果把 [Model Answer]() 迭代 players 陣列的方式，從 forEach 改成  for…in，像這樣：

<pre class="prettyprint">
let players = [
  { name: "櫻木花道", pts: 0, reb: 0, ast: 0, stl: 0, blk: 2 },
  { name: "流川楓", pts: 30, reb: 6, ast: 3, stl: 3, blk: 0 },
  { name: "赤木剛憲", pts: 16, reb: 10, ast: 0, stl: 0, blk: 5 },
  { name: "宮城良田", pts: 6, reb: 0, ast: 7, stl: 6, blk: 0 },
  { name: "三井壽", pts: 21, reb: 4, ast: 3, stl: 0, blk: 0 }
];

const dataPanel = document.querySelector("#data-panel");

function displayPlayerList(data) {
  let htmlContent = "";

  for (let index in data) { // 改這行
    const player = data[index]  // 改這行
    htmlContent += "&lt;tr>";
    
    for (let key in player) {
      if (key === "name") {
        htmlContent += `&lt;td>${player[key]}&lt;/td>`;
      } else {
        htmlContent += `
          &lt;td>&lt;span style="font-size: 25px">${player[key]}&lt;/span>
            &lt;i class="fa fa-plus-circle up" aria-hidden="true">&lt;/i>
            &lt;i class="fa fa-minus-circle down" aria-hidden="true">&lt;/i>
          &lt;/td>
        `;
      }
    }; // 改這行
    htmlContent += "&lt;/tr>";
  };

  dataPanel.innerHTML = htmlContent;
}

displayPlayerList(players);
</pre>

目前的情況下的確可以成功產生出按鈕。但增加 players 陣列的屬性之後，例如，在呼叫 displayPlayerList(players); 的前一行加上這段程式碼：
<pre class="prettyprint">
players.broken = "broken"
displayPlayerList(players);
</pre>

**Q1：會發生什麼事？**
**Q2：為什麼會產生這種狀況？**

<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise3" aria-expanded="false" aria-controls="exercise3">參考答案</a>

<div class="collapse" id="exercise3">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    Q1 :表格最下方多出一行奇怪的東西
    <p>Q2 :這是因為，for … in 在迭代陣列時，除了迭代陣列的 index，也會迭代陣列的其他屬性。</p>
    <p>詳細請參考下一個單元【選修】作業延伸練習：參考答案說明</p>
  </div>
</div>

---

# 【選修】Week 3 作業延伸練習：參考答案說明

請先回答上一個單元的練習題後，再來閱讀參考答案。

#### Exercise 1：了解 innerHTML、innerText、textContent 的不同

**參考答案：目前的使用情境，使用 textContent 比較好。**

##### 1.為什麼不用 innerHTML：

只是要取出字串的話，殺雞焉用牛刀，不需要出動到 innerHTML。因為 innerHTML 會連 HTML 的格式也一起取出。如果哪天網頁改版，分數欄位加上了一些 HTML 格式，例如題目的 HTML 結構，得分欄從 `<td>0</td>` 改成 `<td><div>0</div></td>`，像這樣：

<pre class="prettyprint">
&lt;div class="container">
  &lt;h1 class="my-5">湘北籃球隊計分板&lt;/h1>
  &lt;ol>
    &lt;li>找出&lt;strong>得分數&lt;/strong>大於等於 20 分的球員，在他們的名字旁加上 &lt;i class="fa fa-thumbs-up"> &lt;/i> 符號&lt;/li>
    &lt;li>你只能使用 JavaScript&lt;/li>
    &lt;li>已設定 Font Awesome，只需要創造元素並套用 &lt;code>class="fa fa-thumbs-up"&lt;/code>&lt;/li>
  &lt;/ol>

  &lt;table class="scoreboard table table-bordered table-hover text-center">
    &lt;thead>
      &lt;tr>
        &lt;th>球員&lt;/th>
        &lt;th>得分&lt;/th>
        &lt;th>籃板&lt;/th>
        &lt;th>助攻&lt;/th>
        &lt;th>抄截&lt;/th>
        &lt;th>阻攻&lt;/th>
      &lt;/tr>
    &lt;/thead>
    &lt;tbody>
      &lt;tr>
        &lt;td>櫻木花道&lt;/td>
        &lt;td>&lt;div>0&lt;/div>&lt;/td>
        &lt;td>14&lt;/td>
        &lt;td>0&lt;/td>
        &lt;td>0&lt;/td>
        &lt;td>2&lt;/td>
      &lt;/tr>
      &lt;tr>
        &lt;td>流川楓&lt;/td>
        &lt;td>&lt;div>30&lt;/div>&lt;/td>
        &lt;td>6&lt;/td>
        &lt;td>3&lt;/td>
        &lt;td>3&lt;/td>
        &lt;td>0&lt;/td>
      &lt;/tr>
      &lt;tr>
        &lt;td>赤木剛憲&lt;/td>
        &lt;td>&lt;div>16&lt;/div>&lt;/td>
        &lt;td>10&lt;/td>
        &lt;td>0&lt;/td>
        &lt;td>0&lt;/td>
        &lt;td>5&lt;/td>
      &lt;/tr>
      &lt;tr>
        &lt;td>宮城良田&lt;/td>
        &lt;td>&lt;div>6&lt;/div>&lt;/td>
        &lt;td>0&lt;/td>
        &lt;td>7&lt;/td>
        &lt;td>6&lt;/td>
        &lt;td>0&lt;/td>
      &lt;/tr>
      &lt;tr>
        &lt;td>三井壽&lt;/td>
        &lt;td>div>21&lt;/div>&lt;/td>
        &lt;td>4&lt;/td>
        &lt;td>3&lt;/td>
        &lt;td>0&lt;/td>
        &lt;td>0&lt;/td>
      &lt;/tr>
    &lt;/tbody>
  &lt;/table>
&lt;/div>
</pre>

那麼 innerHTML 版本的做法就會失效，無法在得分數 ≥ 20 的球員名字旁加上讚的符號。

這是因為 `playerRecords[i].children[1].innerHTML` 取出的值會變成字串，例如 `“<div>0</div>"`，於是經過 `parseInt` 的計算之後，最終 `score` 變數裡儲存的值會是 `NaN`，導致 `score >= 20` 的值是 `false`。可以用 console.log 觀察到這個現象，像這樣：

<pre class="prettyprint">
// 用 innerHTML
const playerRecords = document.querySelectorAll('.table tbody tr')
const thumbsUpIconHTML = '&lt;i class="fa fa-thumbs-up">&lt;/i>'

for (let i = 0; i < playerRecords.length; i++) {
  const score = parseInt(playerRecords[i].children[1].innerHTML, 10)
  console.log("innerHTML: ", playerRecords[i].children[1].innerHTML)
  console.log("score: ", score)
  console.log("score >= 20: ", score >= 20)
  console.log("=====我是分隔線=====")
  if (score >= 20) {
    playerRecords[i].children[0].innerHTML += thumbsUpIconHTML
  }
}
</pre>

印出來的內容：

<div style="width:100%"> <a href="https://assets-lighthouse.alphacamp.co/uploads/image/file/21566/Screen_Shot_2022-06-25_at_18.12.22.png" target="_blank"><img style="max-width:700px;width:100%;" src="https://assets-lighthouse.alphacamp.co/uploads/image/file/21566/Screen_Shot_2022-06-25_at_18.12.22.png"></a></div>

若是使用 innerText 或 textContent，即使多了 div 標籤，依然可以在得分數 ≥ 20 的球員名字旁加上讚的符號。

##### 2. 為什麼不用 innerText

如果使用 innerText 和 textContent 程式都可以順利運作的話，建議優先使用 textContent，避免產生[回流](https://developer.mozilla.org/zh-TW/docs/Glossary/Reflow)，可以節省瀏覽器的效能。

因為 innerText 在某種程度上會計算 CSS 樣式（可以參考：[JavaScript學習筆記－－innerText 與 textContent](https://uu9924079.medium.com/javascript%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-innertext-%E8%88%87-textcontent-755b8b93f13b)）瀏覽器為了確保取得的 CSS 樣式是最新的，於是引發了回流。

「回流」在現階段是一個比較進階的概念，目前不太能理解也沒關係，可以先對「如果使用 innerText 和 textContent 程式都可以順利運作的話，建議優先使用 textContent」這件事有個印象就好。


#### Exercise 2：了解使用 document.createElement 可能會踩到的坑，以及對「如何提升程式碼的效能」有初步的認識

**參考答案：擺在 B 和 C 都可以達成「在得分數 ≥ 20 的球員名字旁加上讚的符號」的目標，而擺在 C 的效能會比較好。**

##### 1.為什麼不能放在 A

執行後會發現，只有三井壽名字旁有讚。實際上發生的事情是，因為從頭到尾只有產生一個讚的 icon，icon 會先放到流川楓名字旁，最後又被移動到三井壽名字旁。我們可以用 console.log 觀察到觀察 icon 被移動的過程，像這樣：

<pre class="prettyprint">
const playerRecords = document.querySelectorAll('.table tbody tr')
// A
const thumbsUpIcon = document.createElement('i')
thumbsUpIcon.classList.add('fa', 'fa-thumbs-up')

for (let i = 0; i < playerRecords.length; i++) {
  console.log(`====== for 回圈第 ${i} 圈開始 ======`)
  const score = parseInt(playerRecords[i].children[1].textContent, 10)
  // B
  if (score >= 20) {
    // C
    playerRecords[i].children[0].appendChild(thumbsUpIcon)
  }
  console.log(playerRecords[1].children[0].innerHTML)
  console.log(playerRecords[4].children[0].innerHTML)
  console.log(`====== for 回圈第 ${i} 圈結束 ======`)
}
</pre>

印出來的內容：

<div style="width:100%"> <a href="https://assets-lighthouse.alphacamp.co/uploads/image/file/21567/Screen_Shot_2022-06-25_at_18.09.34.png" target="_blank"><img style="max-width:700px;width:100%;" src="https://assets-lighthouse.alphacamp.co/uploads/image/file/21567/Screen_Shot_2022-06-25_at_18.09.34.png"></a></div>

##### 2. 為什麼放在 C 比放在 B 好

放在 B：
<pre class="prettyprint">
const playerRecords = document.querySelectorAll('.table tbody tr')
// A
for (let i = 0; i < playerRecords.length; i++) {
  const score = parseInt(playerRecords[i].children[1].textContent, 10)
  // B
  const thumbsUpIcon = document.createElement('i')
  thumbsUpIcon.classList.add('fa', 'fa-thumbs-up')
  if (score >= 20) {
    // C
    playerRecords[i].children[0].appendChild(thumbsUpIcon)
  }
}
</pre>
會變成每次 for 回圈一定會宣告一個新的 thumbsUpIcon 變數，但如果球員的得分數不符合「得分數 ≥ 20」，這個變數就用不到。變成讓電腦花費時間和記憶體，去宣告一個不會用到的東西。

放在 C：
<pre class="prettyprint">
const playerRecords = document.querySelectorAll('.table tbody tr')
// A
for (let i = 0; i < playerRecords.length; i++) {
  const score = parseInt(playerRecords[i].children[1].textContent, 10)
  // B
  if (score >= 20) {
    // C
    const thumbsUpIcon = document.createElement('i')
    thumbsUpIcon.classList.add('fa', 'fa-thumbs-up')
    playerRecords[i].children[0].appendChild(thumbsUpIcon)
  }
}
</pre>

則能保證，電腦花費時間和記憶體宣告的 thumbsUpIcon 變數，一定會被使用到。

#### Exercise 3：迭代的對象是陣列中的元素時，建議不要使用的迴圈方法

**參考答案：表格最下方多出一行奇怪的東西。這是因為，for … in 在迭代陣列時，除了迭代陣列的 index，也會迭代陣列的其他屬性。**

##### 1.表格最下方多出一行奇怪的東西，如圖

<div style="width:100%"> <a href="https://assets-lighthouse.alphacamp.co/uploads/image/file/21568/Screen_Shot_2022-06-25_at_22.33.34.png" target="_blank"><img style="max-width:700px;width:100%;" src="https://assets-lighthouse.alphacamp.co/uploads/image/file/21568/Screen_Shot_2022-06-25_at_22.33.34.png"></a></div>

##### 2. 為什麼會產生這種狀況？

這是因為，for … in 在迭代陣列時，除了迭代陣列的 index，也會迭代陣列的其他屬性。因為

<pre class="prettyprint">
players.broken = "broken"
</pre>
這一行幫 players 陣列新增了名叫「broken」的屬性，屬性的值是 "broken” 這個字串，因此也會迭代到這個屬性，並在內層的 for…in 回圈迭代 "broken” 這個字串（字串第 0 個位置是字母 b，字串第 1 個位置是字母 r ⋯⋯依此類推），最終產生我們看到的畫面。

我們可以使用 console.log 觀察到這個現象：

<pre class="prettyprint">
let players = [
  { name: "櫻木花道", pts: 0, reb: 0, ast: 0, stl: 0, blk: 2 },
  { name: "流川楓", pts: 30, reb: 6, ast: 3, stl: 3, blk: 0 },
  { name: "赤木剛憲", pts: 16, reb: 10, ast: 0, stl: 0, blk: 5 },
  { name: "宮城良田", pts: 6, reb: 0, ast: 7, stl: 6, blk: 0 },
  { name: "三井壽", pts: 21, reb: 4, ast: 3, stl: 0, blk: 0 }
];

const dataPanel = document.querySelector("#data-panel");

function displayPlayerList(data) {
  let htmlContent = "";

  for (let index in data) {
    console.log(`===== 外層 for in 回圈 index ${index} 開始 =====`)
    const player = data[index]
    console.log("player: ", player)
    htmlContent += "&lt;tr>";
    
    for (let key in player) {
      console.log(`----- 內層 for in 回圈 key ${key} 開始 -----`)
      console.log("player[key]: ", player[key])
      if (key === "name") {
        htmlContent += `&lt;td>${player[key]}&lt;/td>`;
      } else {
        htmlContent += `
          &lt;td>&lt;span style="font-size: 25px">${player[key]}&lt;/span>
            &lt;i class="fa fa-plus-circle up" aria-hidden="true">&lt;/i>
            &lt;i class="fa fa-minus-circle down" aria-hidden="true">&lt;/i>
          &lt;/td>
        `;
      }
      console.log(`----- 內層 for in 回圈 key ${key} 結束 -----`)
    };
    htmlContent += "&lt;/tr>";
    console.log(`===== 外層 for in 回圈 index ${index} 結束 =====`)
  };

  dataPanel.innerHTML = htmlContent;
}

players.broken = "broken"
displayPlayerList(players);
</pre>

在新增 broken 屬性後，for…in 回圈會額外印出的資訊如下：

<div style="width:100%"> <a href="https://assets-lighthouse.alphacamp.co/uploads/image/file/21569/Screen_Shot_2022-06-25_at_22.45.55.png" target="_blank"><img style="max-width:700px;width:100%;" src="https://assets-lighthouse.alphacamp.co/uploads/image/file/21569/Screen_Shot_2022-06-25_at_22.45.55.png"></a></div>


參考文章：MDN 文件在 for…in 的文件中有個小節 [Array iteration and for...in](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#array_iteration_and_for...in) 建議，如果我們需要處理的只有陣列裡的元素（也就是，用整數 index 就可以存取的那些元素，例如 `array[0]`, `array[1]` 等等），沒有要處理陣列的其他屬性時，不要使用 for…in 來迭代，而是改成使用 forEach, for…of 或傳統的 for 迴圈（也就是 `for (let i = 0; i < array.length; i++) {}` 這種格式的迴圈）



---

# 【選修】Week 4 作業延伸練習

>**學習成果與目標**
>・針對同學在作業中常見的問題延伸練習


恭喜你完成 Week 4 作業來到這個單元，能夠操作 DOM、設置事件，讓網頁開始能夠「動起來」是不是覺得又能夠實踐更多功能、更有成就感了！

如果想要幫助自己更熟練、或是繼續延伸學習，來練習回答以下題目吧：

#### Exercise 1：My Favorite Movies：按讚與刪除 延伸練習

繼作業 My Favorite Movies：按讚與刪除 功能之後，畫面又新增了「超級讚」的綠色按鈕，按一下可以幫電影的 Rating 加十分，像這樣：

<div style="width:100%"> <a href="https://assets-lighthouse.alphacamp.co/uploads/image/file/21693/ezgif-1-1f91f6f8c6.gif" target="_blank"><img style="max-width:700px;width:100%;" src="https://assets-lighthouse.alphacamp.co/uploads/image/file/21693/ezgif-1-1f91f6f8c6.gif"></a></div>

**Q: 除了原本的按讚與刪除功能，請加碼實作「按綠色按鈕加十分」的功能**

起手式程式碼：https://codepen.io/rossignol/pen/ExEjywQ
(請接續完成 JavaScript 的部分)


<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise1" aria-expanded="false" aria-controls="exercise1">參考答案</a>

<div class="collapse" id="exercise1">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    CodePen：https://codepen.io/rossignol/pen/abYOZwq

  </div>
</div>


#### Exercise 2：練習思考網頁改版時，程式碼的易維護性

觀察這份 [CodePen](https://codepen.io/rossignol/pen/xxWGjmK)：

- 觀察紅色按鈕的刪除電影功能是否正常運作
- 老闆說要新增電影收藏功能，於是這份 CodePen 經歷了網頁改版：新增了 class name 是 btn-danger 的紅色愛心按鈕（請把第 20~50 行變成註解，把第 53~84 行的註解取消，以模擬網頁改版）

結果，網頁改版後，點擊紅色愛心按鈕時，電影會被刪除。導致我們需要回過頭去修改刪除電影的邏輯，以免按下愛心按鈕時發生不符合需求的情況。

**Q：一開始在撰寫刪除電影的邏輯時，有什麼比對刪除按鈕的寫法，可以不受這次的網頁改版影響？**


<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise2" aria-expanded="false" aria-controls="exercise2">參考答案</a>

<div class="collapse" id="exercise2">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    分析問題成因：為什麼按紅色愛心按鈕，居然會把電影刪掉？
    <p>該份 CodePen 的 class name：</p>

    <li>btn 是 bootstrap 用來控制按鈕樣式的</li> 
    <li>btn-sm 是 bootstrap 用來控制尺寸的</li> 
    <li>btn-danger 是 bootstrap 用來控制按鈕顏色的</li> 

    <p>該份 CodePen 使用 btn-danger 這個 class name 比對刪除按鈕。而網頁改版後，增加了 classname 一樣是 btn-danger 的 bootstrap 按鈕，於是紅色愛心按鈕也會比對成功，執行刪除電影的邏輯。</p> 

    <p>回到原本的問題：一開始在撰寫刪除電影的邏輯時，有什麼比對刪除按鈕的寫法，可以不受這次的網頁改版影響？</p> 

    <p>不要使用 bootstrap 的 class name 比對元素，而是在 HTML 中自創專屬的 class name 用於比對元素。那麼程式就不會在網頁改版，新增其他 bootstrap 按鈕後，發生奇怪的事情。像這樣：[CodePen](https://codepen.io/rossignol/pen/xxWGjex)，網頁改版後，按下紅色愛心按鈕，也不會意外刪掉電影了。</p> 

  </div>
</div>


    
#### Exercise 3：注意監聽事件時，若忘記宣告參數，可能會不小心使用到已棄用的全域變數 [Window.event](https://developer.mozilla.org/en-US/docs/Web/API/Window/event)

觀察[這份 CodePen](https://codepen.io/rossignol/pen/VwXLxVa)：

- 觀察紅色按鈕的刪除電影功能是否正常運作
- 把第 53 行變成註解，把第 54 行的註解取消，再次觀察紅色按鈕的刪除電影功能是否正常運作


**Q：為什麼把參數 event 刪掉後，刪除的功能沒有壞掉，也沒有跳出「沒有宣告 event 變數」之類的錯誤訊息呢？**


<small>將你的答案分享到留言區與同學交流</small>

<a class="btn btn-secondary" role="button" data-toggle="collapse" href="#exercise3" aria-expanded="false" aria-controls="exercise3">參考答案</a>

<div class="collapse" id="exercise3">
  <div style="padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px;">
    因為剛好有個已經被棄用的全域變數，變數名稱也叫做 event：Window.event，而且變數裡儲存的東西，功能和本來的 event 參數相同。因此在這些機緣巧合之下，刪掉 event 參數之後，會變成使用全域變數 event，於是功能一切都正常。

    <p>為了避免使用到已棄用的全域變數 event，要記得在 event handler 加上參數。也就是，要寫成第 53 行那樣，不要寫成第 54 行那樣</p>

  </div>
</div>