###### 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">
<div class="parent">
<div class="red"></div>
<div class="green"></div>
<div class="blue"></div>
</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><div class="parent"></p>
<p><div class="blue"></div></p>
<p><div class="green"></div></p>
<p> <div class="red"></div></p>
<p> </div>
</div>
</div>
</div>
#### Exercise 2: 瞭解預設屬性的概念
##### Q2: 將起手式程式碼放入自己的 codepen 中,將 CSS 中的註解解除,觀察頁面的變化,思考看看為什麼會有這種變化呢?
#### HTML 起手式程式碼
<pre class ="prettyprint">
<div>
<h1>我是 h1 標題</h1>
</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 = '<i class="fa fa-thumbs-up"></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 = '<i class="fa fa-thumbs-up"></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 = '<i class="fa fa-thumbs-up"></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 += "<tr>";
for (let key in player) {
if (key === "name") {
htmlContent += `<td>${player[key]}</td>`;
} else {
htmlContent += `
<td><span style="font-size: 25px">${player[key]}</span>
<i class="fa fa-plus-circle up" aria-hidden="true"></i>
<i class="fa fa-minus-circle down" aria-hidden="true"></i>
</td>
`;
}
}; // 改這行
htmlContent += "</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">
<div class="container">
<h1 class="my-5">湘北籃球隊計分板</h1>
<ol>
<li>找出<strong>得分數</strong>大於等於 20 分的球員,在他們的名字旁加上 <i class="fa fa-thumbs-up"> </i> 符號</li>
<li>你只能使用 JavaScript</li>
<li>已設定 Font Awesome,只需要創造元素並套用 <code>class="fa fa-thumbs-up"</code></li>
</ol>
<table class="scoreboard table table-bordered table-hover text-center">
<thead>
<tr>
<th>球員</th>
<th>得分</th>
<th>籃板</th>
<th>助攻</th>
<th>抄截</th>
<th>阻攻</th>
</tr>
</thead>
<tbody>
<tr>
<td>櫻木花道</td>
<td><div>0</div></td>
<td>14</td>
<td>0</td>
<td>0</td>
<td>2</td>
</tr>
<tr>
<td>流川楓</td>
<td><div>30</div></td>
<td>6</td>
<td>3</td>
<td>3</td>
<td>0</td>
</tr>
<tr>
<td>赤木剛憲</td>
<td><div>16</div></td>
<td>10</td>
<td>0</td>
<td>0</td>
<td>5</td>
</tr>
<tr>
<td>宮城良田</td>
<td><div>6</div></td>
<td>0</td>
<td>7</td>
<td>6</td>
<td>0</td>
</tr>
<tr>
<td>三井壽</td>
<td>div>21</div></td>
<td>4</td>
<td>3</td>
<td>0</td>
<td>0</td>
</tr>
</tbody>
</table>
</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 = '<i class="fa fa-thumbs-up"></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 += "<tr>";
for (let key in player) {
console.log(`----- 內層 for in 回圈 key ${key} 開始 -----`)
console.log("player[key]: ", player[key])
if (key === "name") {
htmlContent += `<td>${player[key]}</td>`;
} else {
htmlContent += `
<td><span style="font-size: 25px">${player[key]}</span>
<i class="fa fa-plus-circle up" aria-hidden="true"></i>
<i class="fa fa-minus-circle down" aria-hidden="true"></i>
</td>
`;
}
console.log(`----- 內層 for in 回圈 key ${key} 結束 -----`)
};
htmlContent += "</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>