# +_JS30 1~10 不要看都在亂寫 [TOC] # 01 ## addEventListener > window.addEventListener() 是一個監聽事件的 API,當事件觸發時,會執行指定的 callback function。 其中的參數會是:("此監聽事件的名稱", 此事件要做的 callback function) ## keydown 事件 > 當按下鍵盤上的任意鍵時,這段程式碼會被執行,而 keyboard 是"點選了哪個鍵"的該物件 ```javascript! window.addEventListener("keydown", function (keyboard) { console.log(keyboard); }); ``` 例如我點了 enter 鍵,印出來就會是一個帶有 key: "Enter" 這個資訊的 KeyboardEvent: ![image](https://hackmd.io/_uploads/r19DDA5jR.png) ```javascript! window.addEventListener("keydown", function (keyboard) { console.log("按下了鍵盤上的一個鍵:", keyboard.key); }); ``` ![image](https://hackmd.io/_uploads/BJxCwA5s0.png) 所以這個監聽 API 執行了 keydown 事件,幫我監聽了我點選哪個按鍵,然後我可以在 callback function 看我要幹啥!(你想幹啥就幹啥) ## document.querySelector > querySelector 方法用來選取符合指定 CSS 選擇器的第一個 DOM 元素。可以選擇特定的標籤、類名、ID 或屬性。 我的 html 有一個 audio tag ```html! <audio data-key="65" src="sounds/clap.wav"></audio> ``` ```javascript! window.addEventListener("keydown", function (keyboard) { const audio = document.querySelector('audio[data-key="65"]'); console.log(audio); }); // 每次監聽到我按下按鍵,都會抓到 audio 這個 tag 的[data-key="65"] 這個屬性,然後印出來 ``` 印出來會是啥!瘋狂按每個按鍵就是印出這個 65 的 tag 內容 ![image](https://hackmd.io/_uploads/r1MWJTYo0.png) 那要我按什麼就印什麼呢?! babe what should I do 讓我們將 `document.querySelector('audio[data-key=""]')` "" 中的參數改為 keyboard 變數試試,這個 keyboard 就是監聽到的物件 ```javascript! window.addEventListener("keydown", function (keyboard) { const audio = document.querySelector(`audio[data-key="${keyboard}"]`); console.log(audio); }); ``` 沒拿到東西!為什麼! ![image](https://hackmd.io/_uploads/BJj2zTYi0.png) 因為 keyboard 是一个 KeyboardEvent 物件,而不是一个可以直接用於 data-key 屬性的值。 於是我們要指定印出物件其中的值,才可以啦,例如 keyCode ```javascript! window.addEventListener("keydown", function (keyboard) { const audio = document.querySelector(`audio[data-key="${keyboard.keyCode}"]`); // 選取所有 <audio> 標籤,並且 data-key 屬性值等於當前按鍵的 keyCode 的元素 console.log(audio); }); ``` ![image](https://hackmd.io/_uploads/rJp6WTYsA.png) ### GPT 解釋一下啊 ```javascript! window.addEventListener:這是一個 API 用來監聽指定的事件。 事件參數:keyboard 是事件回調函數的參數,當事件發生時,它會傳遞一個事件對象(在這裡是 KeyboardEvent)。這個對象包含有關事件的詳細信息,例如被按下的鍵的 keyCode、key、altKey 等屬性。 document.querySelector:這是一個 DOM 方法,用來選取符合指定 CSS 選擇器的第一個元素。在這個例子中,audio[data-key="${keyboard.keyCode}"] 的意思是選取所有 <audio> 標籤,並且 data-key 屬性值等於當前按鍵的 keyCode 的元素。 結合:當用戶按下鍵盤時,keydown 事件會觸發,你的回調函數會接收到一個 KeyboardEvent 對象,然後通過 keyboard.keyCode 獲取按下的鍵的 keyCode。接著你使用這個 keyCode 來選取對應的 <audio> 元素。 ``` ## audio.play() 這是 for `<audio>` tag 用的,播放功能 ## audio.currentTime [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/currentTime) 設置歸零的話會是有一個 reset 的功用:將播放時間設置為 0,即重新從頭播放 ## classList.add / classList.remove / classList.toggle > 要操作在哪個class名稱上.classList.動作('要加上的 class') ![image](https://hackmd.io/_uploads/ryl9pn5sR.png) ```javascript! key.classList.add('playing') key.classList.remove('playing') key.classList.toggle('playing') // 偵測有沒有 key 這個 class ,沒有的話會加上它 / 有的話會移除 ``` ## document.querySelectorAll() ```javascript! const keys = document.querySelectorAll(".key"); // 印出一個 NodeList ``` ![image](https://hackmd.io/_uploads/SkkIHk12C.png) ![image](https://hackmd.io/_uploads/S1jyryJ20.png) ## transitioned 事件 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/transitionend_event) 點選 A,結果將這些東東都印出來了 ![image](https://hackmd.io/_uploads/SkraLykhA.png) 只要印 propertyName 是 transform 的(為什麼???? A 的 this 是 addEventListener 的 key ![image](https://hackmd.io/_uploads/SJeAvkJh0.png) # 02 先看一下 html 結構,是三隻針都疊在9點:) ![image](https://hackmd.io/_uploads/r1WIB6Gn0.png) 想要讓他在12點鐘方向並且是垂直站著的。 我們先讓他 轉90度 ![image](https://hackmd.io/_uploads/ryVHSpfnC.png) 咦,怎麼變成在自己這邊轉,因為!預設會是由自己的中心轉 於是我們要改變一下 transform-origin 也就是轉的原點 設置為 `transform-origin:100%`(x軸的 100%) ![image](https://hackmd.io/_uploads/HJsDBTM2A.png) 就成功拉 ## Date() ![image](https://hackmd.io/_uploads/rJcZS0Gh0.png) ## getSeconds ![image](https://hackmd.io/_uploads/Hy5LSCMnC.png) ## Date() 是一個 Class 本身是一個類別可以有 get . set 等等行為,因此可以用 Date() 來理解類別的行為,而用 valueOf() 可以拿到最接近未處理的資料 ![image](https://hackmd.io/_uploads/B1X7wAM3C.png) ## 印出每秒的秒數 於是我們可以做出一個 function 去模擬印出每秒的秒數 ```javascript! function setTime() { const now = new Date(); const seconds = now.getSeconds(); console.log(seconds); } setInterval(setTime, 1000); ``` ### 換成度數到秒針上 ```javascript! const secondHand = document.querySelector("second-hand"); function setTime() { const now = new Date(); const seconds = now.getSeconds(); const secondsDegrees = (seconds / 60) * 360; secondHand.style.transform = `rotate(${secondsDegrees}deg)`; } ``` # Playing with CSS Variables and JS 先將要改變型態的東東設上 css ```javascript! :root { --base: #f3c944; --spacing: 10px; --blur: 10px; } img { padding: var(--spacing); background: var(--base); filter: blur(var(--blur)); } .hl { color: var(--base); } ``` 先選到這三個 input ![image](https://hackmd.io/_uploads/SJWdMlEn0.png) ```javascript! const input = document.querySelectorAll('.controls input') ``` 設定事件 ```javascript! const input = document.querySelectorAll(".controls input"); function handleUpdate() { console.log(this.value); } // 每個 input 都監聽 change 事件,並且執行 handleUpdate function input.forEach((input) => input.addEventListener("change", handleUpdate)); input.forEach((input) => input.addEventListener("mousemove", handleUpdate) ); ``` ## change 事件 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/change_event) ## mousemove 事件 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/mousemove_event) ## data data 是一個物件 - 後面是他的屬性,我有一個 input 元素有 data-sizing 的屬性 ```javascript! <input id="blur" type="range" name="blur" min="0" max="25" value="10" data-sizing="px"/> <script> const input = document.querySelectorAll(".controls input"); function handleUpdate() { console.log(this.dataset); } input.forEach((input) => input.addEventListener("change", handleUpdate)); input.forEach((input) => input.addEventListener("mousemove", handleUpdate) ); // 為什麼要加 mousemove 只是因為要在拖曳的時候要隨時抓到值 </script> ``` 我可以用 dataset 這個屬性把他的 value 印出來 ![image](https://hackmd.io/_uploads/rkjbZZVn0.png) 所以我如果另外設置其他的,data- 也是會有 ```javascript! <input id="blur" type="range" name="blur" min="0" max="25" value="10" data-sizing="px" data-mood="unhappy" data-interest="sleep"/> ``` ![image](https://hackmd.io/_uploads/HkZwW-EhR.png) 所以可以用物件方式取得 ```javascript! console.log(this.dataset.sizing); ``` spacing 和 blur 會印出 px,color 的就會印出 undefined ![image](https://hackmd.io/_uploads/BJ_r7WNnR.png) 所以要加一個 ```javascript! const suffix = this.dataset.sizing || ""; ``` ![image](https://hackmd.io/_uploads/Syie6E4nA.png) ## documentElement MDN:Document.documentElement 會回傳目前文件(document)中的根元素(Element),如:HTML 文件中的 <html> 元素。 ![image](https://hackmd.io/_uploads/SkZyAr42A.png) ## documentElement.style 拿到元素後,直接用 .style 方式設置 inline style 其實這邊用 .body.style 也是可以,就會改在 body 上 ![image](https://hackmd.io/_uploads/Sy5RWUN30.png) ## setProperty() .style 要寫 inline style 後,.setProperty 就是可以指定屬性名稱並寫入值! ```javascript! element.style.setProperty(propertyName, value, priority); // propertyName, value 都用字串形式 ``` ```javascript! document.documentElement.style.setProperty( `--${this.name}`, `${this.value}${suffix}` // 後綴記得要加上 ); // 所以說監聽到哪個 element 就改他的 name, 也改成他的 value ``` ![image](https://hackmd.io/_uploads/BJhfiS42C.png) 完成....... 好不直覺,好難!!!!但我的 Jerry 很可愛 > 可愛鼠了 [name=橘子] # 04 ## Array.prototype.filter() 1. Filter the list of inventors for those who were born in the 1500's ```javascript! const ageFilter = inventors.filter((inventor) => { return inventor.year < 1800; }); 可以簡寫為 const ageFilter = inventors.filter((inventor) => inventor.year < 1800); console.table(ageFilter); ``` ![image](https://hackmd.io/_uploads/BJrRg3hJyx.png) ## Array.prototype.map() 2. Give us an array of the inventors first and last names ```javascript! const fullName = inventors.map( (inventor) => inventor.first + " " + inventor.last ); console.table(fullName); ``` ![image](https://hackmd.io/_uploads/SyewW3nyJl.png) ## Array.prototype.sort() 3. Sort the inventors by birthdate, oldest to youngest ```javascript! const sortBirth = inventors.sort(function (inventorA, inventorB) { if (inventorA.year > inventorB.year) { return 1; } else { return -1; } }); const sortBirth = inventors.sort((inventorA, inventorB) => inventorA.year > inventorB.year ? 1 : -1 ); console.table(sortBirth); ``` ![image](https://hackmd.io/_uploads/SkzMV33kyl.png) ## 6. create a list of Boulevards in Paris that contain 'de' anywhere in the name https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris ```javascript! const category = document.querySelector(".mw-category"); const links = Array.from(category.querySelectorAll("a")); const de = links .map((link) => link.textContent) .filter((streetName) => streetName.includes("de")); ``` ```javascript! document.querySelector(".mw-category"); ``` return 的是一個 NodeList,可以用 `Array.from` 或 `[...]` 展開成一個陣列。 ## 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", ]; ``` 關鍵:用 reduce 初始化值設置為一個物件 `{}`,數到後、就加一次 `obj[item] = 0;` 但這個是為了在第一次的時候初始值。 例如 `obj[car]` 第一次應該會是 NaN。 ```javascript! const countDataTimes = data.reduce((obj, item) => { if (!obj[item]) { obj[item] = 0; } obj[item]++; return obj; }, {}); console.log(countDataTimes); ``` # 05 script 部分: 先想好要拿到誰,目前想要操作的是這五個 div,監聽點擊事件、之後對其加上 class ![image](https://hackmd.io/_uploads/BJTOrNCkJg.png) 因此要先拿到這五個 div。 ```javascript! document.querySelectorAll(".panel") ``` ![image](https://hackmd.io/_uploads/HkXxLE0yyl.png) (用 querySelector 只會拿到第一個) 接下來要對每一個做什麼呢? 1. 用 forEach 遍歷其中的元素 2. 監聽 click 事件、觸發 function 讓他加上 open 這個 class ```javascript! function toggleOpen() { this.classList.toggle("open"); } panels.forEach((panel) => { panel.addEventListener("click", toggleOpen); }); ``` 可以用 `classList.toggle()` 動態加上 class ```javascript! classList.toggle("要加上的 class 名稱") ``` 接下來因為要在整個展開的時候,將文字放進來。 可以先看 panel 整個展開之後會做什麼。 ```javascript! function toggleActive(e) { console.log(e); } panels.forEach((panel) => { panel.addEventListener("transitionend", toggleActive); }); ``` 因為加上是 open 這個 class ![image](https://hackmd.io/_uploads/Syps54Akke.png) 所以就有做這兩件事 ![image](https://hackmd.io/_uploads/ryrpYNRJkg.png) 現在不知道為什麼解答寫是在 `propertyName.includes("flex")` 的時候,要加上 .opne-active 而不是 `font` 的時候,應該是因為想要讓他們同時字變大!?? 或可能都可以?:) 現在我們要做的是: `function toggleActive() {}` 在其中把 open.active class 這個加上 ```javascript! function toggleActive(e) { if (e.propertyName.includes("font")) { this.classList.toggle("open-active"); } } ``` open.active 要注意加上之後是要指定其中的第一個和第三個 p 做動作。 ```javascript! .panel.open-active p:nth-child(1) { transform: translateY(0); font-size: 2rem; } .panel.open-active p:nth-child(3) { transform: translateX(0); font-size: 2rem; } ``` # 06 - Type Ahead 1. 取得 api 的資料,整理格式 2. 監聽 input 的輸入 3. 取得輸入的資料,並與所有資料比對,過濾出想要拿到的資料 4. 將想要拿到的資料放到介面上,對介面的東西做視覺(黃色標示、格式化) 將資料轉化成一段 html code 然後再把監聽的 DOM 元素的 innerHTML 改為這段 ## fetch fetch 是一個 api,會回傳 Promise fetch 之後,我們取得一個 Promise,而在 Prototype 裏,看到 [json](https://developer.mozilla.org/zh-CN/docs/Web/API/Response/json) 的一個方法! ![image](https://hackmd.io/_uploads/Hk5I8I1gke.png) `.json()` 可將格式整理為 `json`,並回傳一個 Promise Promise 中我們觀察到,拿到了結果,是一個 Array ![image](https://hackmd.io/_uploads/S1838U1xJx.png) 為什麼叫 blob? 感覺是一個 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob) 說的: > Blob 对象表示一个不可变、原始数据的类文件对象 而剛剛拿到的是 Promise 的結果,我們要取得來做事情,必須要再 .then,所以我們把它 .then 然後 log 出來,就真的拿到資料惹! ![image](https://hackmd.io/_uploads/By72uI1lyg.png) 接下來我們要把它存到一個地方做事,首先先定義一個 cities 的 array,然後再 push 進去: ![image](https://hackmd.io/_uploads/r1DhlDJlkl.png) 成功接收到。 ## 展開運算子對付二維陣列 但目前 cities 它是一個 nest 的陣列,它有一個裡面是 1000 個元素的元素。 可以在要 push 進去的時候展開他。 等於你會把要 push 進去的陣列展開,例如 [1,2,3,4,5] 展開變 1,2,3,4,5 裝進去,那就會在第一層。 ![image](https://hackmd.io/_uploads/Hkg3VwJe1l.png) cities 就變成貨真價實的 1000 個元素了。 接下來要製作一個 function 是可以偵測到你輸入的字,以及去查找 cities 裡面匹配的東西。 ## 字串匹配正則 [String.prototype.match()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/match) match 裡面可以放 `/字串/i` 這樣就會完全匹配裡面的字,例如我寫 `/Bos/i`,function 丟 "Bos" 就會回傳匹配的 ![image](https://hackmd.io/_uploads/SylTpKVxyg.png) ## [正則使用](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp) ```javascript! new RegExp(這裡面放你寫正則的驗證) ``` EX ```javascript! new RegExp(wordToMatch, "gi"); ``` gi:The gi modifier is used to do a case-insensitive search of all occurrences of a regular expression in a string. 不區分大小寫的! 接下來要做的是當我們在 input 輸入東西的時候,要有介面的變化 要怎麼做? 監聽 input 拿到值,之後顯示匹配的東西,並且更新 ui。 監聽和顯示 都要用到 DOM 所以可以先拿這兩個地方的 DOM 輸入框的 ![image](https://hackmd.io/_uploads/HJW4N9Ng1l.png) 顯示的白色區塊 ![image](https://hackmd.io/_uploads/HyRwN9Egyx.png) 接下來我們監聽 input 的 change 事件,然後印出當下輸入的值看看 ```javascript! function displayMatches() { console.log(this.value); } const searchInput = document.querySelector(".search"); const suggestion = document.querySelector(".suggestion"); searchInput.addEventListener("change", displayMatches); ``` ![image](https://hackmd.io/_uploads/ry0485VeJe.png) 沒問題,所以我們可以用這個 function 去顯示匹配的結果然後顯示 ui ```javascript! function displayMatches() { const matchArray = findMatches(this.value, cities); console.log(matchArray); } ``` ![image](https://hackmd.io/_uploads/S1l8_c4lyg.png) ## replace 的方法 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace) 步驟寫在 code 註解.... # 08 學 Canvas [第十七章、你好啊鐵人們,接下來就由我鎖鏈 Canvas 對付你!(壹)](https://ithelp.ithome.com.tw/articles/10248443) ## [getContent()](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/getContext) ```javascript! canvas.getContext("2d") // "2d", 建立一个 CanvasRenderingContext2D 二维渲染上下文。這邊就是可以畫畫的地方。 ``` ## [.strokeStyle](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors) [CanvasRenderingContext2D:strokeStyle 属性](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/strokeStyle) 畫筆的顏色? ## [.lineJoin](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineJoin) 畫筆的樣式? ## [.lineCap](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineJoin) 畫筆末端的樣式? 設置一個 flag 控制:滑鼠點擊的時候可畫畫,放開時就不能畫畫。 所以我們先將一個變數設置為 false,然後監聽滑鼠的 mousemove 事件,當事件被觸發, ## 畫畫套組 [beginPath. moveTo . lineTo. stroke](https://www.w3schools.com/jsref/tryit.asp?filename=tryhtml5_canvas_moveto) stroke 是畫實線 ## [fillRect](https://www.w3schools.com/jsref/tryit.asp?filename=tryhtml5_canvas_fillrect) ## [strokeRect](https://www.w3schools.com/jsref/tryit.asp?filename=tryhtml5_canvas_strokerect) mousemove 事件印出來會是 ![image](https://hackmd.io/_uploads/HyfYZvPxkx.png) 因此我們可以知道,這就是滑鼠位置的座標,offsetX,offsetY 我們可以拿來作為 lineTo 也就是我們滑鼠到哪裡,就畫到哪裡 但由於 lastX, lastY 都是 0,所以會變成這樣 ![image](https://hackmd.io/_uploads/Bym3XHigyg.png) 從 0 畫到 滑鼠 mousemove 的位置 因此我們的 moveTo 初始地點,需要改一下 應該設置為 `lastX = e.offsetX; lastY = e.offsetY;` 但現在因為初始值依舊是 0,所以我們等於從 0 開始畫,然後後來接著 mousemove 的地方 event 的 offsetX,offsetY 畫,變成醬子 ![image](https://hackmd.io/_uploads/Syg94Hsxkl.png) 這時候我們應該想著我們畫畫的行為 mousedown 會在 mousemove 之前發生,因此我們要在 mousedown 前就設置 `lastX = e.offsetX; lastY = e.offsetY;`! ![image](https://hackmd.io/_uploads/S1jatrixJg.png) 就可以自由的畫畫啦 接下來要設置畫筆顏色和粗細 畫筆顏色: [hsl](https://developer.mozilla.org/zh-CN/docs/Web/CSS/color_value/hsl)(0,100,50); 0 是紅色,100 飽和度 100,50 代表正常 ```javascript! ctx.strokeStyle = `hsl(0, 100%, 50%)`; ``` 接下來我們因為要變顏色,所以我們可以在第一個參數設置成變數(這邊設置為 hue) ```javascript! ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`; ``` 以及我們在最後設置 ```javascript! hue ++; ``` ![image](https://hackmd.io/_uploads/H1FhSUieye.png) 因此就會有顏色變化 畫筆粗細: `lineWidth` 畫筆粗細變化: 這邊注意是到一個極限值就又變小 等於會是:細變粗,粗變細 這邊做法是先設置一個 direction = true 代表要控制畫筆粗細的條件 所以我們先讓畫筆變粗 ```javascript! // 先讓畫筆變粗,讓他加到 100,加到 100 之後會進 else 開始變小,變小到 1 之後又會變相反,又進 if 開始加 if (ctx.lineWidth === 100 || ctx.lineWidth === 1) { direction = !direction; } if (direction) { ctx.lineWidth++; } else { ctx.lineWidth--; } ``` # 09 console.log 的怪怪用法 ## 插值用法 %s ```javascript! console.log('Hello I am a %s string','🙁') ``` ![image](https://hackmd.io/_uploads/SkjKyvolJg.png) ## css 用法 %c ![image](https://hackmd.io/_uploads/BkFMlwsl1x.png) ![image](https://hackmd.io/_uploads/HkQVeDox1e.png) ![image](https://hackmd.io/_uploads/BylPeDsgJg.png) 跟字有關的就好,隨你玩 ## warn ![image](https://hackmd.io/_uploads/B1TslPjx1g.png) ## error ![image](https://hackmd.io/_uploads/S1EAePjxkx.png) ## info ![image](https://hackmd.io/_uploads/rJGNbDigkl.png) ## assert ![image](https://hackmd.io/_uploads/SyKqbDilke.png) 前面這段是錯的才會顯示 ```javascript! console.assert(p.classList.contains('ouch'),'it is wrong!') ``` 這會檢查 p 有沒有 'ouch' 這個 class 沒有就會顯示 ![image](https://hackmd.io/_uploads/B1cSUwslyg.png) ## clear ```javascript! console.clear() ``` ![image](https://hackmd.io/_uploads/rJ5udvse1e.png) 就是 clear ## dir 可以看到整個 DOM 元素的東東 ![image](https://hackmd.io/_uploads/Skk1tPslJe.png) ## group 可以設置 group 和 groupEnd ```javascript! dogs.forEach((dog) => { console.log(`${dog.name}`); console.log(`${dog.name} is ${dog.age} years old`); console.log(`${dog.name} is ${++dog.age} years old next year`); }); dogs.forEach((dog) => { console.group(`${dog.name}`); console.log(`${dog.name}`); console.log(`${dog.name} is ${dog.age} years old`); console.log(`${dog.name} is ${++dog.age} years old next year`); console.groupEnd(`${dog.name}`); }); ``` ![image](https://hackmd.io/_uploads/HJXWsDslyx.png) 也可以設置 groupCollapsed `console.groupCollapsed(`${dog.name}`);` 一開始預設就會是關著的 ![image](https://hackmd.io/_uploads/rJiCjDogyx.png) ## count 可以算出在 count 中用了幾次 ![image](https://hackmd.io/_uploads/r18i3vsg1e.png) ## time 可以算時間 `console.time(設置要算時間的東西)` ```javascript! console.time("fetch endpoint"); fetch("https://api.github.com/users/wesbos") .then((data) => data.json()) .then((data) => { console.timeEnd("fetch endpoint"); console.log(data); }); ``` ![image](https://hackmd.io/_uploads/BJURgOjekl.png) # 10 click 事件有 .shiftKey 可以看到是否是按著 shift 點擊的 這一題有個範圍因此可以先設置標記最後點擊的,最後點擊的就是在 shit 前點擊的。 所以監聽點擊事件後先將 this 設置為 lastChecked 再判斷如果是 shiftKey 的話,做什麼事情,這邊因為 shiftKey 按著取消 checkbox 也會觸發所以應該要是確認是在按下 shift 並且 checkbox 被勾選的時候觸發