# 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')。這樣做提高了代碼的清晰度和可移植性。
=