# JavaScript is Hard as Rock ## Basic Scope: let v.s var What are the printing results? - Scope ```javascript if(true){ var x = 3; } console.log(x); ``` 函數作用域(function-scoped) 在函數外宣告,則是全域作用域(globally-scoped) thus, x = 3 (global) ```javascript if(true){ let x = 3; } console.log(x); ``` 區塊作用域(block-scoped),只在其宣告的區塊(如 if、for 等)內有效 導致一個引用錯誤(ReferenceError),因為 x 在這個作用域中並不存在。所以,該程式碼執行時將會報錯,而不會列印出任何數值。 ## Hoisting: var 宣告在前 var 會被提升,但只有宣告。 ```javascript console.log(x); var x = 3; ``` undefined ```javascript console.log(x); let x = 3; ``` (ReferenceError),因為 x 在這個時刻還未被宣告。 ```javascript var x = 3; function test(){ x = 10; var x = 5; } test(); console.log(x); ``` 在 test 函數內部,var x = 5; 的宣告會因 hoisting 被提升至函數作用域的頂部。 當執行 x = 10; 時,實際上是修改了函數作用域內的 x,而不是全域作用域的 x。 因此,當 test() 函數執行完畢後,全域變數 x 的值仍然是 3。 ## CallBack ```javascript let text = "hey!"; console.log(1); function useless(tryCallback) { console.log(2); return tryCallback(); } function getText() { console.log(3); return text; } console.log(4); if(useless(getText) === text){ console.log(5 + text); } console.log(6); ``` 接下來,console.log(4); 列印 4。注意到這時 function useless 和 function getText 都已經被定義,但尚未被調用。 if(useless(getText) === text) 條件句開始執行。 首先,useless(getText) 被調用: 在 useless 函數內部,console.log(2); 列印 2。 然後調用 tryCallback(),即 getText() 函數: 在 getText 函數內部,console.log(3); 列印 3。 getText 返回 text 的值,即 "hey!"。 useless 函數返回 "hey!"。 接著,檢查 if 條件 "hey!" === "hey!",該條件為真。 因此,console.log(5 + text); 列印 5hey!。 最後,console.log(6); 列印 6。 ### Defines a callback function directly as an argument #### Click, API Call ```javascript if(useless(function(){return text;})===text){ console.log(5 + text); } ``` Use for: executing code on a button click, call api... --- ### Event-Driven #### Callbacks can also be called by the browser ```javascript document.body.addEventListener("mousemove", function(){ let second = document.getElementById("second"); addMessage(second, "Event: mousemove"); }); ``` That’s defined as an event handler to the mousemove event, and that will be called by the browser when that event occurs. ### SORT 升序排序 ```javascript let values = [0, 3, 2, 5, 7, 4, 8, 1]; values.sort(function(value1, value2){ // comparator return value1 - value2; }); ``` We provide a callback that the JavaScript engine will call every time it needs to compare two items. ## Promise The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Executor passed to Promise constructor executes synchronously. ```javascript new Promise((resolve)=>{ console.log(10) resolve(3); }).then((data)=>{ console.log(data); }).catch((error)=>{ console.log(error); }); console.log("end"); ``` ```javascript async function example() { console.log(10); const data = await Promise.resolve(3); console.log(data); } example().catch((error) => { console.log(error); }); console.log("end"); // What is the printing result? ``` 當新的 Promise 被創建時,傳遞給它的執行器函數(executor function)立即同步執行。因此,console.log(10) 會首先被執行,列印 10。 resolve(3) 被調用,標記 Promise 為成功(fulfilled)狀態,並將值 3 作為結果。 .then((data) => { console.log(data); }) 排定了一個當 Promise 解決時執行的回調函數。然而,這個回調函數不會立即執行,而是會在當前執行棧清空後的微任務隊列(microtask queue)中被排定。 console.log("end") 接著被執行,列印 "end"。 最後,當前執行棧清空後,微任務隊列中的 .then 回調被執行,列印出 3。 ### Promise.all() Promise.all() 方法接受一個 Promise 對象的數組作為參數。 它返回一個新的 Promise,這個 Promise 在所有傳入的 Promise 都成功解決(fulfilled)時解決,並將每個 Promise 的結果組成一個數組作為結果。 ### Promise.any() Promise.any() 接受一個 Promise 對象的數組作為參數。 它返回一個 Promise,這個 Promise 在任何一個傳入的 Promise 解決時解決,並將第一個解決的 Promise 的結果作為結果。 如果所有傳入的 Promise 都失敗,則返回的 Promise 會以一個 AggregateError 為原因而失敗。 ### Promise.race() Promise.race() 同樣接受一個 Promise 對象的數組作為參數。 它返回一個 Promise,這個 Promise 會採用第一個解決或失敗的傳入 Promise 的狀態和結果作為自己的狀態和結果。 換句話說,無論是解決還是失敗,第一個改變狀態的 Promise 決定了 Promise.race() 返回的 Promise 的結果。 ## Arrow Function Arrow functions are a simplification of function expressions. ```javascript let func=function(name){ return "Greetings "+name; }; let arrowFunc1=(name)=>{ return "Greetings " + name; }; let arrowFunc2=(name)=>("Greetings "+name); let arrowFunc3=name=>"Greetings "+name; ``` Arrow functions cannot be used as constructors. ```javascript let Point=(x,y)=>{ this.x=x; this.y=y; }; let p=new Point(3,4); // What will happen to this? ``` 在這個例子中: 箭頭函數不能用作構造函數,這意味著您不能使用 new 關鍵字來創建箭頭函數的實例。 如果您嘗試這樣做(像上面的代碼那樣),JavaScript 將會拋出一個錯誤,提示說箭頭函數不能作為構造函數使用。 not a constructor ```javascript document.addEventListener("click", function(){ console.log(this); // What is "this" here? }); document.addEventListener("click", ()=>{ console.log(this); // What is "this" here? }); ``` 在使用普通函數表達式的第一個 addEventListener 調用中,this 會被設置為觸發事件的元素,即 document。 在使用箭頭函數的第二個 addEventListener 調用中,箭頭函數不綁定自己的 this,它會捕獲其包含作用域中的 this 值。在這個例子中,如果這段代碼是在全域作用域中運行的,箭頭函數中的 this 很可能指向全域對象(在瀏覽器中是 window);如果這段代碼是在某個封閉的作用域或模塊中運行,this 的值將依該作用域而定。 # What are the printing results? 1. ```javascript let result=subtract(1,2); function subtract(a, b){ // declaration return a-b; }; console.log(result); ``` -1 ⭕️ 函數宣告會被提升(hoisting),因此在代碼執行時,函數 subtract 已經可用。 2. ```javascript let result=subtract(1,2); let subtract=function(a, b){ // expression return a-b; }; console.log(result); ``` 這裡會發生錯誤。因為在嘗試調用 subtract 函數時,它還沒有被定義。函數表達式不會被提升。Referrence Error 3. ```javascript let a=1; function b(){ a=10; return; function a(){} } b(); console.log(a); ``` 10 ❌ 您的答案是 10,但實際上結果應該是 1。在函數 b 中,function a(){} 創建了一個同名局部變數 a,並且由於函數提升,它覆蓋了外部作用域的變數 a。因此,a = 10; 實際上修改的是局部變數 a,而不是外部的 a。 4. ```javascript let test=a=>b=>a+b*3; // How to call test correctly? ``` ❌ test(a)(b),如 test(1)(2) 5. ```javascript function test(a, b, ...c){ console.log(a, b, c); } test(1, 2, 3, 4, 5, 6); test(1, 2, 3, 4); test(); ``` ⭕️ 1,2,[3,4,5,6] ⭕️ 1,2,[3,4] ❌ 不正確。實際上會列印 undefined, undefined, [],因為 a 和 b 都是 undefined,而 c 是一個空數組。 6. ```javascript function test(){ console.log("1"); } window.setTimeout(test, 0); console.log("2"); ``` 先 2 再 1 因為 setTimeout 機制 ⭕️ 即使 setTimeout 的延遲時間設置為 0,回調函數 test 也會在當前執行棧清空後的任務隊列中排隊 https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/EventLoop 7. ```jsx <button id="btn">Click</button> <script> // Is this code snippet do what it wants to do? btn.addEventListener("click", function(){ // Want to delay 1 second and do something window.setTimeout(function(){ // Want to change button text color to red this.style.color="red"; }, 1000); }); </script> ``` `const btn = documument.getElementById("btn")` change 'this' to 'btn' ⭕️ setTimeout 內的匿名函數中的 this 不會指向按鈕元素,而是指向全域對象(在瀏覽器中是 window) ## 閉包解決 ```javascript // 获取按钮元素 btn.addEventListener("click", function(){ // 延迟 1 秒后执行 window.setTimeout(function(){ // 修改按钮文字颜色为红色 this.style.color = "red"; }, 1000); }); ``` ⭕️ 或是使用箭頭函數: ```javascript btn.addEventListener("click", function() { window.setTimeout(() => { this.style.color = "red"; }, 1000); }); ``` ⭕️ 或是直接重新賦予 this: ```javascript btn.addEventListener("click", function() { const button = this; window.setTimeout(function() { button.style.color = "red"; }, 1000); }); ``` ⭕️⭕️ 甚至使用 bind: ```javascript btn.addEventListener("click", function() { window.setTimeout(function() { this.style.color = "red"; }.bind(this), 1000); }); ``` 👉 btn 變量實際上已經是按鈕元素的引用,因為它是按鈕的 id 屬性。在大多數現代瀏覽器中,具有 id 屬性的 HTML 元素會自動成為全域變量。 然而,這種自動創建全域變量的做法並不是最佳實踐,因為它依賴於瀏覽器的特定行為,並且可能會導致代碼難以理解和維護。更好的做法是明確地獲取元素引用,如使用 document.getElementById('btn')。這樣做提高了代碼的清晰度和可移植性。 =