# J_JS30 01-10
[TOC]
> 這個單元如果要學得扎實,首先透過前面幾章先理解網頁的基本API,熟悉一些寫法,後面的章節看影片時聽到他說今天要做什麼功能就先按暫停,先自己絞盡腦汁想出辦法,也可以問人,寫好以後再看影片的寫法對答案,解法不是唯一,但在自己探索的過程中可以學到更多方法,我是這樣學的。
> [name=Chris]
我只寫我聽懂的,聽不懂的沒講到的麻煩大家幫我補充:cry:
[JS30 github](https://github.com/wesbos/JavaScript30)
去將他clone下來就可以看到他的教材了
# 01-JavaScript Drum Kit
打開`01 - JavaScript Drum Kit`的`index-START.html`就可以開始了
一開始網頁中就有寫好的按鈕跟要播放的音效,開始寫`<script>`吧!
首先是對`window`註冊事件`"keydown"`,當我按下鍵盤時執行後面function,也就是將按下那個按鍵的資訊印出來:
```javascript=
window.addEventListener("keydown", (keyboardInput) => {
console.log(keyboardInput);
});
```
所以我按下鍵盤他就把對應的資訊印出來:

可以觀察到裡面有個`keyCode`屬性,所以我可以寫:
```javascript=
window.addEventListener("keydown", (keyboardInput) => {
console.log(keyboardInput.keyCode);
});
```
所以我按下鍵盤他就會把對應的`keyCode`印出來:

再來試著取得某個標籤:
```javascript=
window.addEventListener("keydown", (keyboardInput) => {
const audio = document.querySelector(`audio[data-key='${keyboardInput.keyCode}']`);
console.log(audio);
});
```
使用`document.querySelector()`去取得DOM裡面的元素`<audio>`,這邊我要取得的是`data-key`這個屬性為我按下的按鍵`keyCode`的元素,結果就可以取道對應的元素:

但如果按了鍵盤但頁面中沒有對應的元素就會接收到`null`
再來就是設定按下鍵盤播放對應的元素音檔:
```javascript=
window.addEventListener("keydown", (keyboardInput) => {
const audio = document.querySelector(`audio[data-key='${keyboardInput.keyCode}']`);
if (!audio) return; //取到null的時候就在這邊結束,不要往下跑
audio.play();
});
```
寫好以後就可以去網頁中試試看,按下指定的按鍵就會有聲音了~
接下來會遇到的問題是當我連點的時候,我連點好幾次但大部分時候只會播放一次,原因是點擊第一次後播放音檔可能需要兩三秒,但這個過程你繼續點擊叫他播放他會認為他正在播放,但我們想要的應該是每次按都會從頭播放。
解決這個狀況可以在`audio.play()`執行前將他的執行時間歸零,這樣你每次按下按鍵他都會從頭開始:
```javascript=
window.addEventListener("keydown", (keyboardInput) => {
const audio = document.querySelector(`audio[data-key='${keyboardInput.keyCode}']`);
if (!audio) return;
audio.currentTime = 0; //設定從頭開始
audio.play();
});
```
回到網頁就可以確認每次按按鈕都有聲音了,丟需啊內
再來嘗試取得特定標籤:
```javascript
const key = document.querySelector(`.key[data-key='${keyboardInput.keyCode}']`);
console.log(key);
```
這裡取到的就會是`class`為`key`的,且`data-key`為你按下的按鍵對應的`keyCode`:

這邊也可以用別的方式取得,像是`<div>`:
```javascript
const key = document.querySelector(`div[data-key='${keyboardInput.keyCode}']`);
console.log(key);
```
將取得的標籤加上新的`class`屬性:
```javascript
key.classList.add("playing");
```
`playing`這個樣式可以去看`style.css`檔:

大致上就是加上黃色框框跟陰影
所以現在的`<script>`長這樣:
```javascript=
window.addEventListener("keydown", (keyboardInput) => {
const audio = document.querySelector(`audio[data-key='${keyboardInput.keyCode}']`);
const key = document.querySelector(`div[data-key='${keyboardInput.keyCode}']`);
if (!audio) return;
audio.currentTime = 0;
audio.play();
console.log(key);
key.classList.add("playing");
});
```
當我按下鍵盤,對應的那個鍵會發出音效然後加上`playing`這個屬性:

這邊也提到另外幾個API:
```javascript
key.classList.remove("playing");
key.classList.toggle("playing");
```
第一個就是移除,第二個有看沒有懂,他也沒多做解釋,以後用到再說吧
但我們期望的不是黃框框一直存在,而是亮一下就消失,所以要寫另一個函式去移除`playing`這個屬性,但如果直接移除就會變成==屬性一加上去馬上就被移除,肉眼只會看到沒有任何樣式==,所以最簡單是去看他的動畫有多久然後使用`setTimeout`設定一樣的時間後移除屬性:
```javascript
setTimeout(() => {
key.classList.remove("playing");
}, 70);
```
但他就只能固定是這個秒數,萬一其他按鈕動畫不是這個秒數就很奇怪,所以採用另種辨識`transition`時間的方式,讓這個屬性的動畫結束在刪除這個屬性。
較適合做法:
```javascript
const keys = document.querySelectorAll(".key");
console.log(keys);
```
設定一個`keys`來存放所有擁有`.key`屬性的元素,印出來會是這樣,可以發現他是個陣列:

所以我們就可以用array method來處理它:
```javascript=
function removeTransition(e) {
console.log(e);
}
const keys = document.querySelectorAll(".key");
keys.forEach((key) => key.addEventListener("transitionend", removeTransition));
```
在每個按鈕都註冊一個事件處理器,當它發生狀態轉換時就觸發,可以看到按一個按鍵就觸發了好幾個transition(上下左右border等等),但最重要的是`transform`,但也不知道為什麼要特別跳過其他的。

再來要理解函式執行時的this:
```javascript=
function removeTransition(e) {
if (e.propertyName !== "transform") return;
console.log(this);
}
const keys = document.querySelectorAll(".key");
keys.forEach((key) => key.addEventListener("transitionend", removeTransition)
);
```
當我觸發狀態轉換時就執行`removeTransition`,然後他會將當下的`this`印出來,但這邊的`this`是標籤而不是物件(有待討論):

結果是發生狀態轉換的元素本身,然後我們就可以將他的`playing`移除了:
```javascript
this.classList.remove("playing");
```
這樣就完成了練習
整理一下程式碼:
```javascript=
<script>
function playSound(keyboardInput) {
const audio = document.querySelector(`audio[data-key='${keyboardInput.keyCode}']`);
const key = document.querySelector(`.key[data-key='${keyboardInput.keyCode}']`);
if (!audio) return;
audio.currentTime = 0;
audio.play();
key.classList.add("playing");
}
function removeTransition(e) {
if (e.propertyName !== "transform") return;
this.classList.remove("playing");
}
const keys = document.querySelectorAll(".key");
keys.forEach((key) => key.addEventListener("transitionend", removeTransition));
window.addEventListener("keydown", playSound);
</script>
```
# 02-JS and CSS Clock
開始做時鐘!打開檔案後嘗試旋轉他發現並不是我們預期的樣式,沒有特別設定時他將從中心為基準去旋轉:

但我們想讓他以最右邊為基準旋轉,這時候就用到`transform-origin`(只要是`transform`都是用這個調整基準點),參數分別為`(x, y)`軸,`(0, 0)`為左上角,預設會是`(50%, 50%)`也就是box的中心點,所以將旋轉基準點移動到最右側`transform-origin: 100%`,這樣就有如預期了:

[MDN transform-origin](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin)
接下來設定一些動畫參數:

這邊只是讓時鐘動起來比較厲害,太認真會陷得很深...
設定好就可以開始做時鐘的功能了。
先定義一個`getTime`來抓取當下的秒數,然後設定一個`setInterval`來每一秒執行一次`getTime`:
```javascript=
function getTime() {
const now = new Date();
const second = now.getSeconds();
console.log(second);
}
setInterval(getTime, 1000);
```
每秒印出來一次當下的秒數:

設定秒數對應的角度:
```javascript
const secondsDegrees = (seconds / 60) * 360 + 90;
```
因為原本位置是指向數字9的位置(-90度),所以加回90度。
接著抓取屬性是`.second-hand`的元素:
```javascript
const secondHand = document.querySelector(".second-hand");
```
使用`style.transform`來給他動畫:
```javascript
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
```
如此一來秒針就會每一秒都抓取一次時間,根據當下時間來決定要旋轉的角度。
時針跟分針一樣步驟,全部整理好就是:
```javascript=
const secondHand = document.querySelector(".second-hand");
const minHand = document.querySelector(".min-hand");
const hourHand = document.querySelector(".hour-hand");
function getTime() {
const now = new Date();
const seconds = now.getSeconds();
const secondsDegrees = (seconds / 60) * 360 + 90;
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
const mins = now.getMinutes();
const minsDegrees = (mins / 60) * 360 + 90;
minHand.style.transform = `rotate(${minsDegrees}deg)`;
const hours = now.getHours();
const hoursDegrees = (hours / 12) * 360 + 90;
hourHand.style.transform = `rotate(${hoursDegrees}deg)`;
}
setInterval(getTime, 1000);
```
這樣時鐘就做完啦~

有一些小細節可以調整:
* 可以調整三個指針的樣式
* 秒針動的時候分針跟時針有需要動嗎?
* 秒針在數完60秒後會跳一次,因為從360度瞬間變0度
如果設計既算秒數時也加到分針上,計算分鐘時也加到時針上:
```javascript=
function getTime() {
const now = new Date();
const seconds = now.getSeconds();
const secondsDegrees = (seconds / 60) * 360 + 90;
secondHand.style.transform = `rotate(${secondsDegrees}deg)`;
const mins = now.getMinutes();
const minsDegrees = (mins / 60) * 360 + (seconds / 60) * 6 + 90;
minHand.style.transform = `rotate(${minsDegrees}deg)`;
const hours = now.getHours();
const hoursDegrees = (hours / 12) * 360 + (mins / 60) * 30 + 90;
hourHand.style.transform = `rotate(${hoursDegrees}deg)`;
}
```
角度問題很難解釋,歡迎來討論
下一個問題是秒針在數完60秒會跳一下,這個問題橘子的解法是一開始訂好角度,接下來計算時間就累加上去,所以角度會是無限增加,不會有60秒一個循環。
# 03-Playing with CSS Variables and JS
這個單元要做出網頁中有參數可以調整,依據調整會改變底下圖片的顯示。
這邊圖片連結好像壞了,要自己去找圖片。
首先先將樣式寫好:

設了一些變數,之後只要改動`:root`的變數值就好。
設定好以後就來取得`<input>`吧:
```javascript
const inputs = document.querySelectorAll(".controls input");
```

拿到啦!展開`prototype`可以看到有一些method可以用,再來就在每個input都註冊事件處理器,當input發生變化時提取他的值:
```javascript=
function handleUpdate() {
console.log(this.value);
}
inputs.forEach((input) =>
input.addEventListener("change", handleUpdate)
);
```

成功在調整刻度時拿到值。
但現在只能調整完才拿到值,我們的目的可能想要移動刻度的時候畫面也跟著變化,所以使用另一個事件:mousemove
```javascript
inputs.forEach((input) => input.addEventListener("mousemove", handleUpdate));
```

此時我只要游標在`<input>`上移動他就會取得當下的值。
再來介紹`dataset`這個鬼東西。
因為我們取得值之後也需要加上單位我們才能放到`css`中使用,例如我取到`blur`的值是`10`,但要加上單位變成`10px`才能被使用,這個單位他寫在`<input>`中的`data-sizing`裡面,但我們==沒辦法直接取`data-sizing`==,必須透過`dataset`,屬性中以`data-`為前綴的屬性會被存在`dataset`中,我們印出來看看:
```javascript
function handleUpdate() {
console.log(this.dataset);
}
```

看到他是一個物件包含`sizing`屬性,而這個`sizing`就是標籤中的`data-sizing`:

所以我嘗試加入其他的data資料進去看看:


印出來就會有我設定的屬性資料了。
現在我們已經可以取得單位了,將他跟剛剛的`value`結合在一起放到`style`裡面:
```javascript=
function handleUpdate() {
const suffix = this.dataset.sizing || ""; //這裡的意思是取到sizing就設定sizing,沒取到東西就設定""
document.documentElement.style.setProperty(
`--${this.name}`,
this.value + suffix
);
}
inputs.forEach((input) => input.addEventListener("mousemove", handleUpdate));
```
用`documentElement.style.setProperty`設定`style`屬性,再檢查一下我們做了什麼:
* 在每個`<input>`註冊`mousemove`事件觸發`handleUpdate()`
* 觸發`handleUpdate()`會取得當下的`<input>`標籤的`data-sizing`屬性的值,接著將`style`這個屬性根據`<input>`的種類添加到`documentElement`裡面
來看看成果:

將游標移動到`<input>`上他就會添加當下的值進去`<html>`裡面,這個設定值會影響到整個`<html>`裡面的`--spacing`、`--blur`及`--base`的值,也就可以影響圖片的顯示了!
# 04-Array Cardio Day 1
這個單元看起來是在練習一些Array method,他列了8個練習:
## Array.prototype.filter()
1. Filter the list of inventors for those who were born in the 1500's
第一題用`filter`篩選出在15世紀出生的人:
```javascript=
const fifthCentury = inventors.filter(
(inventor) => inventor.year > 1500 && inventor.year < 1600
);
console.table(fifthCentury);
```

上面是原來的表格,成功取得15世紀出生的人。
## Array.prototype.map()
2. Give us an array of the inventors first and last names
第二題用`map`做出inventors的全名陣列:
```javascript=
const fullNames = inventors.map(
(inventor) => `${inventor.first} ${inventor.last}`
);
console.log(fullNames);
```

完成。
## Array.prototype.sort()
3. Sort the inventors by birthdate, oldest to youngest
第三題用`sort`排序從出生年最大到最小:
> 題外話,到這裡我突然發現教學的跟教材的inventors名單不同,影片中的只有7個人,教材裡面有12個人。
簡單來排序吧,就把年份最早的排前面:
```javascript
const ordered = inventors.sort((a, b) => a.year - b.year);
console.log(ordered);
```

完成。
## Array.prototype.reduce()
4. How many years did all the inventors live all together?
第四題用`reduce`計算所有人的得年總和:
```javascript=
const totalYears = inventors.reduce((totalYears, inventor) => {
return totalYears + (inventor.passed - inventor.year);
}, 0);
console.log(totalYears);
```
跟影片不一樣,因為他只有7個人算出來是523
我寫12個人印出來是861,但我不想驗算。
> 我也是算861,這樣算是驗算嗎
> [name=橘子]
> 我也算861,覺得詭異,來了發現我不4一個倫。
> [name=Jamixcs]
然後發現影片裡的名單就是到`Max Planck`這個人,所以教材把後面的都拿掉就是影片的名單了。
拿掉後算出來也是523。
接下來我都改成跟影片一樣的名單好了。
## 5. Sort the inventors by years lived
第五題依照他們的得年排序:
```javascript=
const oldest = inventors.sort((a, b) => {
const lastGuyLived = a.passed - a.year;
const nextGuyLived = b.passed - b.year;
return nextGuyLived - lastGuyLived;
});
console.table(oldest);
```

最上面是活最久的。
## 6. create a list of Boulevards in Paris that contain 'de' anywhere in the name
[參考](https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris)
進入連結將裡面包含`de`的項目篩選出來,所以接下來就進去連結使用devTools來操作:

進到網站中找到所有項目都會放在擁有`class="mw-category"`的標籤裡面:
1. 所以首先取得`<div class="mw-category ..."></div>`這個標籤
2. 將裡面的`<a>`標籤都取出來做成array
3. 在對每個`<a>`做取得裡面的文字
4. 最後搜尋並保留裡面有`de`的項目
```javascript=
const category = document.querySelector('.mw-category');
const links = Array.from(document.querySelectorAll('a'));
const de = links
.map(link => link.textContent)
.filter(streetName => streetName.includes('de'));
console.log(de)
```

## 7. sort Exercise
Sort the people alphabetically by last name
回到教材的檔案名單,第七題是將第二個名單用姓氏的字母順序排列:
```javascript=
const alpha = people.sort((a, b) => {
const [aLast, aFirst] = a.split(", ");
const [bLast, bFirst] = b.split(", ");
return aLast > bLast ? 1 : -1;
});
console.log(alpha);
```

這裡我不太理解他說用last name去排序,我以為last name是後面的,但看起來他的結果是用前面那個字去排。
這邊值得注意的是兩個字串比大小時,會從第一個字母的code去比,如果一樣就比第二個字母的code...
## 8. Reduce Exercise
Sum up the instances of each of these
最後一題,將陣列中的項目數量分別加總:
```javascript=
const data = ["car", "car", "truck", "truck", "bike", "walk", "car", "van", "bike", "walk", "car", "van", "car", "truck",
];
const sumList = data.reduce((resultObj, item) => {
if (!resultObj[item]) { //檢查物件中有沒有這個項目屬性
resultObj[item] = 0; //沒有就建立
}
resultObj[item]++;
return resultObj;
}, {});
console.log(sumList);
```

完成。
如果我將名單最後加上`"Jeremy"`:
```javascript
const data = ["car", "car", "truck", "truck", "bike", "walk", "car", "van", "bike", "walk", "car", "van", "car", "truck", "Jeremy"];
```

成功計算有一個`Jeremy`。
# 05-Flex Panels Image Gallery
打開05資料夾`index-START.html`檔案吧,發現圖片連結好像壞了好幾個,也是要自己換圖片。
這個章節要做的是點擊圖片會有flex伸縮效果,且上下兩排字會掉進來。
一開始的版面不是我們要的排版,複習一下之前學的`flex`將他調成我們要的樣式。
原排版:

調整後排版:

具體來講增加了這些樣式:
前面這些都是一般排版:


上下排的字預設是往外丟,直到加上`open-active`樣式才掉進來:

`panel`加上`open`時放大:

註冊事件處理器:
```html=
<script>
const panels = document.querySelectorAll(".panel");
function toggleOpen() {
this.classList.toggle("open");
}
function toggleActive(e) {
if (e.propertyName.includes("flex")) {
this.classList.toggle("open-active");
}
}
panels.forEach((panel) =>
panel.addEventListener("click", toggleOpen)
);
panels.forEach((panel) =>
panel.addEventListener("transitionend", toggleActive)
);
</script>
```
`toggleActive`裡面要做的判斷是因為事件`transitionend`會捕捉所有的樣式變化,如果不篩選其中一個,他就可能因為辨識到好幾個樣式變化而跑好幾次,所以特別只判斷有沒有`flex`的變化,具體來講`panel`加上`open`後的`transition`變化有`font-size`跟`flex-grow`兩個,所以他瞬間執行兩次感覺上被抵銷了:

後來我多加一個`color`的`transition`進去,所以現在`transtiion`變成了三個:


就成功有效果了!

觀察後的結論就是一次觸發兩個事件有可能會被抵銷,所以==盡可能讓觸發事件的次數單純==。
教學就到這邊而已。
不管怎麼截圖檔案都太大= =,只能截一小塊:

# 06-Type Ahead
這章要做搜尋關鍵字的範例,先獲取外部的資料然後在`input`輸入文字,會搜尋所有符合的項目且列在下方。
打開就有一個連結,他是一個`json`形式的檔案,我們先用`fetch`來取得這個檔案的內容然後印出來看看:
```javascript
const endpoint = "https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json";
console.log(fetch(endpoint));
```
他是一個Promise!!但還不知道他傳出來的資料是什麼:

所以我們用`.then()`去接出來看看是什麼:
```javascript
fetch(endpoint).then(console.log);
```

結果還是看不懂,但教學有說用裡面的`.json()`方法去處理它:
```javascript
fetch(endpoint)
.then((blob) => blob.json())
.then(console.log);
```


就看出來是一個很多資料的陣列,裡面有各個城市的資訊。
拿到資料後來存進一個陣列方便以後只用吧:
```javascript=
<script>
const endpoint =
"https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json";
const cities = [];
fetch(endpoint)
.then((blob) => blob.json())
.then((data) => cities.push(...data));
</script>
```

成功放進`cities`裡面等等再來操作他~
接下來寫幾個函式跟取得包含`search`跟`suggestions`的標籤:
```javascript=
function findMatches(words, cities) {
return cities.filter((place) => {
const regex = new RegExp(words, "gi");
return place.city.match(regex) || place.state.match(regex);
});
}
function displayMatches() {
const matchArray = findMatches(this.value, cities);
console.log(matchArray);
}
const searchInput = document.querySelector(".search");
const suggestions = document.querySelector(".suggestions");
searchInput.addEventListener("keyup", displayMatches);
```
**findMatches()**
`findMatches`會在`cities`中篩選出有包含傳入的`words`,回傳一個包含結果的陣列。
這邊他用了正規表達式...有機會再研究一下
**displayMatches()**
接著取得包含`.search`的`<input>`標籤,註冊事件,當鍵盤作動時就觸發`displayMatches`,那他就會將`<input>`標籤中的值丟進`findMatches`執行,最後印出過濾的結果陣列!

接下來這段比較麻煩,目的在創建html元素:
```javascript=
function addCommasToNumber(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function displayMatches() {
const matchArray = findMatches(this.value, cities);
const html = matchArray
.map((place) => {
const regex = new RegExp(this.value, "gi");
const cityName = place.city.replace(
regex,
`<span class="hl">${this.value}</span>`
);
const stateName = place.state.replace(
regex,
`<span class="hl">${this.value}</span>`
);
return `
<li>
<span>${cityName}, ${stateName}</span>
<span>${addCommasToNumber(place.population)}</span>
</li>
`;
})
.join("");
suggestions.innerHTML = html;
}
```
簡單講就是==將剛剛得到的`matchArray`轉換成想要在網頁中建立的元素==,影篇分成幾個步驟,但我覺得很麻煩就直接寫最終版本:
* regex是正規表達式,內容會根據使用者輸入的內容
* 再來想要生成的區塊,先來看一下結果:

* 其中我們希望符合的字加上`hl`(highlight)樣式
所以我們先將剛剛得到的`matchArray`做`map`處理,把裡面的每個元素都先換成標籤代碼。用正規表達式`regex`來抓取完全符合的字,將他替換成`<span class="hl">${this.value}</span>`,好像有點難想像:cry:
以上面的例子來講,就是將本來物件中的`Boston`
替換成`<span class="hl">Boston</span>`
然後放到`cityName`裡面,`stateName`也是一樣的步驟
還有人口數也加進去,這邊用了`addCommasToNumber`這個函式,目的是要將一串數字加上逗點,他也用正規表達式,就照抄吧:
```javascript
function addCommasToNumber(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
```
最後再將它們組起來:
```html
<li>
<span>${cityName}, ${stateName}</span>
<span>${addCommasToNumber(place.population)}</span>
</li>
```
然後在`map`區塊`return`,這樣就做好我們想要的陣列了。
最後這一連傳的陣列用`join("")`組起來,放進`suggestions`這個區塊中
然後就做完了:

完整程式碼:
```html=
<script>
const endpoint =
"https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json";
const cities = [];
fetch(endpoint)
.then((blob) => blob.json())
.then((data) => cities.push(...data));
function findMatches(words, cities) {
return cities.filter((place) => {
const regex = new RegExp(words, "gi");
return place.city.match(regex) || place.state.match(regex);
});
}
function addCommasToNumber(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function displayMatches() {
const matchArray = findMatches(this.value, cities);
const html = matchArray
.map((place) => {
const regex = new RegExp(this.value, "gi");
const cityName = place.city.replace(
regex,
`<span class="hl">${this.value}</span>`
);
const stateName = place.state.replace(
regex,
`<span class="hl">${this.value}</span>`
);
return `
<li>
<span>${cityName}, ${stateName}</span>
<span>${addCommasToNumber(place.population)}</span>
</li>
`;
})
.join("");
suggestions.innerHTML = html;
}
const searchInput = document.querySelector(".search");
const suggestions = document.querySelector(".suggestions");
searchInput.addEventListener("keyup", displayMatches);
</script>
```
# 07-Array Cardio Day 2
這個章節也是在練習Array method:
1. Array.prototype.some(), is at least one person 19 or older?
只需要知道陣列中有人有任何人至少19歲,所以使用`Array.prototype.some()`:
```javascript=
const isAdult = people.some((person) => {
const currentYear = new Date().getFullYear();
return currentYear - person.year >= 19;
});
console.table(people);
console.log(isAdult); //true
```
這跟`Array.prototype.filter()`有點像,需要回傳一個布林值讓他判斷,所以只要有一個人年齡至少19歲,整個`some()`就會回傳`true`:

2. Array.prototype.every(), is everyone 19 or older?
這次要用`Array.prototype.every()`判斷是不是所有人都至少有19歲:
```javascript=
const allAdult = people.every(
(person) => new Date().getFullYear() - person.year >= 19
);
console.log(allAdult); //false
```
這次變成只要有一個人不到19歲就回傳`false`。
3. find the comment with the ID of 823423
取得其中一個符合的資料用`Array.prototype.find()`:
```javascript=
const comment = comments.find((comment) => comment.id === 823423);
console.table(comments);
console.log(comment);
```

5. delete the comment with the ID of 823423
首先要先選中目標元素的索引值:
```javascript=
const index = comments.findIndex(
(comment) => comment.id === 823423
);
console.table(comments);
console.log(index);
```

目標的索引值是`1`。
再來想刪掉選中的元素,影片中先示範用了`Array.prototype.splice()`,但這個方法會改動到原來的`comments`:
```javascript=
const index = comments.findIndex(
(comment) => comment.id === 823423
);
comments.splice(index, 1);
console.table(comments);
```

雖然成功刪除目標元素,但從此以後他就消失在名單了,如果這符合需求也是沒問題,但如果我們需求是想要保留原本的名單而另外建立新的目標名單,可以用`Array.prototype.slice()`然後放入新的陣列:
```javascript=
const index = comments.findIndex(
(comment) => comment.id === 823423
);
const newComments = [
...comments.slice(0, index),
...comments.slice(index + 1),
];
console.table(newComments);
console.table(comments);
```

上面的新陣列就是刪除目標元素的名單,下面則是原本的名單。
# 08-Fun with HTML5 Canvas
這個單元在建立一個可以畫畫的區塊,有些用法好難懂:cry:
首先提取想要畫畫的`<canvas>`區塊:
```javascript
const canvas = document.querySelector("#draw");
```
接著在這個區塊建立一個畫筆(我完全不懂,只好先這樣理解):
```javascript
const ctx = canvas.getContext("2d");
```
將畫板大小設成視窗大小(看你自己要多大,保持原本的800*800也行):
```html
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
```
一些畫筆的設定:
```javascript
ctx.strokeStyle = "#BADA55"; //畫筆顏色
ctx.lineJoin = "round"; //轉彎處的處理
ctx.lineCap = "round"; //頭尾的處理
ctx.lineWidth = 1; //寬度
```
然後是一些控制狀態的設定:
```javascript
let isDrawing = false; //控制畫筆作動與否,等之後用到再多做解釋
let lastX = 0; //畫筆起始位置X
let lastY = 0; //畫筆起始位置Y
let hue = 0; //調整色碼
let direction = true; //這邊是用來調整粗細,等之後用到再多做解釋
```
```javascript=
function draw(e) {
if (!isDrawing) return; //如果畫筆還沒啟動就不做事
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`; //畫筆顏色根據hue的值決定
//這段就是主要在畫畫的區塊
ctx.beginPath(); //開啟一個新路徑
ctx.moveTo(lastX, lastY); //起點的(x, y)
ctx.lineTo(e.offsetX, e.offsetY); //終點的(x, y)
ctx.stroke(); //使用目前的筆觸樣式畫畫
[lastX, lastY] = [e.offsetX, e.offsetY]; //更新起點
hue++; //改變色碼
if (hue >= 360) {
hue = 0; //色碼360為一個循環,所以到了就歸零
}
if (ctx.lineWidth >= 100 || ctx.lineWidth <= 1) {
direction = !direction; //畫筆大小在碰到1跟100的時候反轉
}
if (direction) {
ctx.lineWidth++;
} else {
ctx.lineWidth--;
}
}
canvas.addEventListener("mousemove", draw);
```
設置好之後還要開啟畫筆,在按下滑鼠的時候開啟:
```javascript
canvas.addEventListener("mousedown", (e) => {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY]; //這邊如果沒有設定他就會還是上次畫完的終點,並不是我們要的效果
});
```
最後設置要關閉畫筆的時機:
```javascript
canvas.addEventListener("mouseup", () => (isDrawing = false));
canvas.addEventListener("mouseout", () => (isDrawing = false));
```
做完啦~

```javascript
ctx.globalCompositeOperation = "multiply";
```
有很多種效果可以去研究:
[MDN globalCompositeOperation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)
# 09-14 Must Know Dev Tools Tricks
這個章節介紹一些DevTools的功能。
1. 查看屬性變化的詳情:

這段文字在點擊後會觸發`makeGreen()`,但我們可能很難找到他做了什麼,點右鍵打開『屬性修正』:

再來就觸發事件:

就會跳到他觸發的行為了,可以看到他加上了顏色,再來放大了字體。
2. 一般的`console.log`
3. Interpolated加入變數
```javascript
console.log("hello %s", "Jeremy"); //hello Jeremy
```
4. Styled加入樣式
```javascript
console.log("%c hello", "color: red; font-size: 50px; background: yellow");
```

5. warning!
```javascript
console.warn("NO!");
```

6. Error
```javascript
console.error("It's an Error!");
```

7. info
```javascript
console.info("15T has Jeremy, Watson, FangFang, Tangerine and Jami.");
```

> 在影片中的年代前面是有一個小小的`i`圖示,現在都拿掉了所以跟`console.log`看起來幾乎沒有區別,跟Chris一起討論的結果是`console.log`可能用來除錯用,印出結果看有沒有符合預期,看完就會拿掉;`console.info`則是真的要展示給使用者看的,有了這個區別後就能更好的分辨哪些是需要留下來的哪些可以不用。
> [name=Chris and Jeremy]
8. Testing
```javascript
const p = document.querySelector("p");
console.assert(p.classList.contains("ouch"), "You didn't select the right Element!");
```

9. clearing
```javascript
console.clear();
```

將之前的訊息清空。
10. Viewing DOM Elements
```javascript
console.log(p);
console.dir(p);
```

查看DOM元素,可以找到可以用的屬性。
10. Grouping together
```javascript=
dogs.forEach((dog) => {
console.group(`${dog.name}`);
console.log(`This is ${dog.name}`);
console.log(`${dog.name} is ${dog.age} years old.`);
console.log(`${dog.name} is ${dog.age * 7} dog years old.`);
console.groupEnd(`${dog.name}`);
});
```

用兩個`group`區塊夾住,將中間內容組成群組。
11. counting
```javascript=
console.count("Jeremy");
console.count("Jeremy");
console.count("Jeremy");
console.count("Watson");
console.count("Watson");
console.count("Watson");
```

12. timing
```javascript=
console.time("fetch time");
fetch("https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json")
.then((data) => data.json())
.then((data) => console.timeEnd("fetch time"));
```

用兩個`time`區塊夾住,可以測出中間執行經過的時間。
13. Table
```javascript
console.table(dogs);
```

最後講到`console.table`,將陣列或物件這種有多筆資料的類型用表格方式展示。
他說14個?我做下來只記了13個,但我懶得回去找,掰掰~
> 後來發現有網友整理,查看DOM元件那邊`console.log`跟`console.dir`算兩個case,如果這樣算就有14個。
# 10-Hold Shift to Check Multiple Checkboxes
這個章節要講的是勾選一個項目,然後按著`shift`勾選另一個項目,將會勾選兩個項目之間的所有項目。
首先選取所有的`checkbox`:
```javascript
const checkboxes = document.querySelectorAll(".inbox input[type=checkbox]");
```
註冊點擊事件:
```javascript=
let lastChecked; //記錄上次勾選的項目
function handleCheck(e) {
let inBetween = false; //用來判斷項目有沒有在兩個勾選的項目之間
if (e.shiftKey && this.checked) {
//當使用者按著shift,且勾選checkbox時
checkboxes.forEach((checkbox) => {
console.log(checkbox);
if (checkbox === this || checkbox === lastChecked) {
//當檢查到的checkbox是使用者當下勾選的,或是先前勾選的
inBetween = !inBetween; //將inBetween狀態換成true
console.log("what's inBetween?"); //在狀態切換時印出來
}
if (inBetween) {
checkbox.checked = true; //將inBetween是true的期間經過的checkbox打勾
}
});
}
lastChecked = this; //每次勾選完都更新成最後勾選的項目
}
checkboxes.forEach((checkbox) =>
checkbox.addEventListener("click", handleCheck)
);
```
`inBetween`狀態為`true`時經過了紅框框的四個項目並將他們勾選起來:
