# 2024-07-05 JavaScript ## Promise的解釋 [面試重點] 問: 什麼是`Promise`? 答: 我不知道我什麼時候會好,但是我好了我保證會告訴你,一定會有結果 ```js /* * Promise * 有三種狀態: * pedding : 創建時,預設狀態 * resolve (Fulfilled) : Promise成功完成後會進入的狀態 * reject : Promise失敗後會進入的狀態 */ ``` [pedding情境] ```js const whatever = function (a, b) {}; const p1 = new Promise(whatever); console.log(p1); ``` ![image](https://hackmd.io/_uploads/SJmdPCwPC.png) ```js const p1 = new Promise(function (resolve, reject) { // true: 進入resolve function // false: 進入reject function let success = false; // new Promise的時候會開始執行 console.log("Start Promise..."); // 等待setTimeout時間到,會根據success判斷進入哪一種狀態 // 只會得到一種結果,不會同時發生resolve和reject setTimeout(() => { if (success) { <===== 3秒後執行 // true -> then() resolve("Promise is resole (o))"); } else { // false -> catch() reject("Promise is reject (x)"); } }, 3000); }); p1.then((r) => { console.log(r); }).catch((r) => { console.log("error", r); }); ``` ```js * async function 內可以有很多個Pr * await fetch() 所回傳的值會是一個Promise, 使用await去等待Promise的結果(resolve或reject) * resolve() -> .then() reject() -> .catch() * reject()的用意是可以做出一個提示頁面(例如:404),引導使用者到對的地方去 ``` ## Event loop 事件循環 [面試題] - Call Stack - 所有function都會逐一被放到call stack - FILO先進後出: First In, Last Out - web Apis - Callback Queue - 瀏覽器內的Queue - FIFO先進先出: First In, First Out (例如:超商排隊結帳) ※ 要等Call Stack內所有工作都做完,才能執行Callback Queue ※ Call Stack在何時會一直累績上去 : 是在function()內在呼叫一層function()在呼叫一層function()... ![image](https://hackmd.io/_uploads/HyVjI0LDR.png) ``` * 同步: 等到每一步都執行完畢,才進行下一步驟 * 非同步: 把需要花時間處理的事情,先丟到另一個地方去 ``` ```js // 1. call stack console.log(1); // 2. call stack // 2-1. webAPI -> wait time out // 4. webAPI -> callback Queue // 5. callback Queue -> call satack setTimeout(() => { console.log(2); }, 1000); // 3. call stack console.log(3); ``` **** callback Queue內的資料一定會等到call stack內沒有任何task,才會從callback Queue放到call stack去執行 ```js // 1. console.log(1); // 2. call stack -> webAPI -> wait // 5. timeout -> webAPI -> callback Queue -> 等待for loop在call stack內執行完畢 // 6. for loop -> end -> 從callback Queue放到call stack上執行 setTimeout(() => { console.log(2); }, 1000); // 3. console.log(3); // 4. for (let i = 0; i < 1000000000; i++) { } ``` **** 所有非同步執行的函數都會被放進webAPI ## <script></script> 在實務上不見得可以放在body 把<script>放到<head>內可以設定兩種屬性 1. defer => <script src="./app.js" `defer`></script> (所以瀏覽器都支援) 2. async => <script src="./app.js" `async`></script> 參考網址: https://pjchender.dev/javascript/js-async-defer/ ![image](https://hackmd.io/_uploads/HkLyjRIwC.png) `scaript正常情況下` 1. HTML文件解析時,只要遇到`<script>`就會先把`HTML parsing pauseed` 2. 開始下載script檔案 3. 下載完畢後,開始執行 4. HTML恢復parsing ![image](https://hackmd.io/_uploads/ByUxi0Iw0.png) `defer` 會讓 scripts 的檔案先開始被下載,但在 HTML 文件準備好後才開始執行,同時會確保各個 script 檔案執行的順序 ![image](https://hackmd.io/_uploads/SyiWsALPC.png) `async` 會讓 scripts 的檔案先開始被下載,但它不會確保各個 script 檔案被執行的順序,先下載好的就先執行 ![image](https://hackmd.io/_uploads/r1tfsA8wA.png) addEventListener也是非同步,會走call stack -> web Apis -> wait 等到`有觸發`的時候才會從web Apis -> callback Queue -> call stack 流程去排隊 (callback function去webApi排隊) ## event loop 要看的影片 https://www.youtube.com/watch?v=8aGhZQkoFbQ&ab_channel=JSConf ## Event Flow 事件流 [面試題] 可分為三個階段: 1. 捕獲階段 capturing 2. 目標階段 targeting 3. 冒泡階段 bubbling [情境一] ```html <body> <div class="aa"> <div class="bb"></div> </div> <script src="./app.js" async></script> </body> ``` ```js .aa { width: 300px; height: 300px; background-color: yellow; display: flex; align-items: center; justify-content: center; } .bb { width: 150px; height: 150px; background-color: pink; } ``` ![image](https://hackmd.io/_uploads/BJaXX0vP0.png) ![image](https://hackmd.io/_uploads/B1UH9G_vA.png) ### event flow 會由上而下 1. 入口 Capturing 2. 目標 Targeting 3. 出口 Bubbling ![image](https://hackmd.io/_uploads/rJZQHMuwC.png) ### addEventListener(type, listener, useCapture) - useCapture : false (預設) 註冊在`出口`的地方 - 使用 `Bubbling` 方向的順序 => 由下往上 ↑ - addEventListener(type, listener) ![image](https://hackmd.io/_uploads/S1mq5MuDR.png) ```html <body> <div class="aa"> <div class="bb"></div> </div> <script src="./app.js" async></script> </body> ``` ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener("click", () => { console.log("AA"); }); bb.addEventListener("click", () => { console.log("BB"); }); ``` ![image](https://hackmd.io/_uploads/SyvvaM_PA.png) - useCapture : true 註冊在`入口`的地方 - 使用 `Capturing` 方向的順序 => 由上往下 ↓ - addEventListener(type, listener, true) ![image](https://hackmd.io/_uploads/SkVxX7OPA.png) ```html <body> <div class="aa"> <div class="bb"></div> </div> <script src="./app.js" async></script> </body> ``` ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener( "click", () => { console.log("AA"); }, true ); bb.addEventListener( "click", () => { console.log("BB"); }, true ); ``` ![image](https://hackmd.io/_uploads/HJ2i6fOPC.png) ### 停止事件傳播 e.stopPropagation() - 可以設定`e.stopPropagation()`停止事件傳播,指的是下一個行為不再傳播 [情境一 設定停止傳播在 bb ] ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener("click", (e) => { console.log("AA"); }); bb.addEventListener("click", (e) => { e.stopPropagation(); <=== 在BB層設定停止傳播,所以就不會在往AA層 console.log("BB"); }); ``` ![image](https://hackmd.io/_uploads/rJOJ1X_vA.png) [情境二 在aa監聽設定true,在bb設定停止傳播 ] ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener( "click", (e) => { console.log("AA"); }, true ); bb.addEventListener("click", (e) => { e.stopPropagation(); console.log("BB"); }); ``` ![image](https://hackmd.io/_uploads/HJ-q-7OwC.png) ![image](https://hackmd.io/_uploads/HJA4Q7uPR.png) [情境三 設定停止傳播在 aa ] ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener("click", (e) => { e.stopPropagation(); <=== 在AA層設定停止傳播,但後面沒東西,所以不會有影響 console.log("AA"); }); bb.addEventListener("click", (e) => { console.log("BB"); }); ``` ![image](https://hackmd.io/_uploads/BJhDl7ODA.png) ### 獲取Target ( 事件觸發元素,元素也就是最底層彎處的target ) - `target` 是整個事件流的最底層彎處的元素 - 不管`AA層`或是`BB層`都會只會指定到同一個`target` [情境一 在aa層獲取target] ```html <body> <div class="aa"> <div class="bb"></div> </div> <script src="./app.js" async></script> </body> ``` ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener("click", (e) => { console.log(e.target); ^^^^^^^^^^^^^^^^^^^^^^ 印出 <div class="aa"> <div class="bb"></div> </div> }); ``` ![image](https://hackmd.io/_uploads/rk0tBQdwC.png) [情境二 在bb層獲取target] ```html <body> <div class="aa"> <div class="bb"></div> </div> <script src="./app.js" async></script> </body> ``` ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener("click", (e) => { console.log(e.target); <=== 印出 <div class="bb"></div> }); bb.addEventListener("click", (e) => { console.log(e.target); <=== 印出 <div class="bb"></div> }); ``` ![image](https://hackmd.io/_uploads/rJaZLmdwA.png) ### 獲取currentTarget (事件監聽器註冊到的元素都會印出) [情境一 點擊觸發aa層] ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener("click", (e) => { console.log(e.currentTarget); }); bb.addEventListener("click", (e) => { console.log(e.currentTarget); }); ``` ![image](https://hackmd.io/_uploads/B1jBjQdDC.png) [情境二 點擊觸發bb層] ```js const aa = document.querySelector(".aa"); const bb = document.querySelector(".bb"); aa.addEventListener("click", (e) => { console.log(e.currentTarget); }); <=== 預設註冊在bubbling bb.addEventListener("click", (e) => { console.log(e.currentTarget); }); <=== 預設註冊在bubbling ``` ![image](https://hackmd.io/_uploads/H1_dim_DC.png) [情境三 設定button,點擊button會將所有div都移除] ```html <body> <div class="aa"> <div class="bb"> <button id="btn">X</button> </div> </div> <script src="./app.js" async></script> </body> ``` ```js const close = document.querySelector("#btn"); close.addEventListener("click", (e) => { console.log(e.currentTarget.closest(".aa")); // e.currentTarget表示選擇到我目前這顆button // e.currentTarget.closest(".aa")表示離我這顆按鈕最近的aa e.currentTarget.closest(".aa").remove(); }); ``` ![image](https://hackmd.io/_uploads/HJ2MgVdD0.png) 所有的div 都會消失不見 (包含aa層和bb層) ![image](https://hackmd.io/_uploads/BkUVg4OvR.png) 在什麼實際案例下會使用到currentTarget (撲克牌遊戲) ![撲克牌](https://hackmd.io/_uploads/rktnx4_D0.png) ## 物件 (待續)