# 📚 ES6+ 的常用語法與特性 - 學習筆記
> [!Note]
> :::spoiler **資料來源**
> - [JavaScript 那個 let, const, var 到底差在哪? - 六角學院](https://www.youtube.com/watch?v=FGdKdn_CnWo&t=2165s)
> - [作用域(scope)解說:let, const, var有什麼差? - 走歪的工程師James](https://www.youtube.com/watch?v=Pychc22EG4Q)
> - [JS var, let, const](https://www.notion.so/casper-wang/JS-var-let-const-6c4d85fc28fc42a9aa2b6c303b276748)
> :::
---
## 🚀 學習進度
- [x] **主題 1:** JavaScript: var, let, const
- [x] **主題 2:** JavaScript: Template Literals(模板字串)
- [x] **主題 3:** JavaScript: Arrow Function (箭頭函式)
- [ ] **主題 4:**
---
## 📍 主題 1:JavaScript — `var`、`let`、`const`
### 🔹 為什麼要宣告變數?
在 JavaScript 中,如果沒有明確宣告變數就直接賦值,該變數會自動成為全域物件(如瀏覽器環境下的 `window`)的屬性,會導致以下問題:
```javascript
function fn() {
a = 0;
}
fn();
```
上述程式碼中,`a` 會變成全域屬性,難以追蹤來源,並可能造成副作用與除錯困難。
---
#### ⚠️ 全域變數衝突範例
```javascript
function fn() {
a = 0;
}
fn();
function fnB() {
a = 1;
}
fnB();
console.log(a); // 輸出:1
```
當程式碼行數眾多,若變數未宣告,可能會被後續覆蓋,造成非預期錯誤。
---
#### ✅ 正確宣告方式
```javascript
function fn() {
var a = 0; // 函式作用域
}
fn();
function fnB() {
var a = 1;
}
fnB();
console.log(a); // ❌ ReferenceError: a is not defined
```
使用 `var`、`let`、`const` 可以避免全域污染。
---
### 🔹 全域與區域污染
#### ⚠️ 未宣告變數造成全域污染
```javascript
a = 0;
function fn() {
a = 1;
}
fn();
console.log(a); // 輸出:1
```
---
#### ✅ 可刪除屬性(未宣告變數)
```javascript
a = 0;
console.log(a);
delete window.a;
console.log(a); // ❌ a is not defined
```
---
#### ✅ 無法刪除已宣告變數
```javascript
var b = 1;
delete window.b;
console.log(b); // 輸出:1
```
---
### 🔹 `var` 的作用域與特性
#### 函式作用域
```javascript
function fn() {
var a = 1;
debugger;
}
fn();
```
---
#### 全域 vs 區域變數
```javascript
var a = 0;
function fn() {
var a = 1;
console.log("local", a); // local 1
}
fn();
console.log("全", a); // 全 0
```
---
#### 改變全域變數的值
```javascript
var a = 0;
function fn() {
a = 1;
}
fn();
console.log("全", a); // 全 1
```
---
#### 詞法作用域
```javascript
var a = 0;
function fnA() {
console.log(a);
}
function fnB() {
var a = 1;
}
fnB();
fnA(); // 輸出:0
```
---
#### 進階題
```javascript
var a = 0;
function fnA() {
console.log(a);
}
function fnB() {
var a = 1;
fnA();
}
fnB(); // 輸出:0
```
---
### 🔹 重複宣告與區塊作用域
#### `var` 可重複宣告
```javascript
function fn() {
var a = 1;
var a = 0;
}
fn();
```
---
#### `{}` 區塊不會限制 `var` 的作用域
```javascript
{
var b = 2;
}
console.log(b); // 輸出:2
```
---
### 🔹 `for` 迴圈與作用域
#### 使用 `var`
```javascript
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // 輸出:10
```
---
#### `var` + `setTimeout`
```javascript
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i); // 全部輸出 10
}, 0);
}
```
---
### 🔹 Hoisting(提升)
```javascript
console.log(a); // undefined
var a = 1;
console.log(a); // 1
```
若完全沒宣告就使用會報錯 `a is not defined`
---
### 🟦 `let` 的特性
### 🔸 作用域:Block-Level Scope
```javascript
{
let a = 1;
}
console.log(a); // ❌ a is not defined
```
```javascript
function fn() {
let a = 1;
}
console.log(a); // ❌ a is not defined
```
---
### 🔸 `let` 在 `for` 中的作用域
```javascript
for (let i = 0; i < 10; i++) {
console.log(i);
setTimeout(() => {
console.log(i); // 輸出:0 ~ 9
}, 0);
}
console.log(i); // ❌ i is not defined
```
每次迴圈的 `i` 都有自己的作用域。
---
### 🔸 `let` 不掛在 `window` 上
```javascript
var a = 0;
let b = 1;
console.log(window); // 只會看到 a
```
---
### 🔸 不可重複宣告
```javascript
let a = 0;
let a = 1; // ❌ Identifier 'a' has already been declared
```
---
### 🔸 暫時性死區(TDZ)
```javascript
console.log(a);
let a = 0; // ❌ Cannot access 'a' before initialization
```
---
### 🔸 與函式參數重複會報錯
```javascript
function fn(a) {
console.log(a);
let a = 2; // ❌ Identifier 'a' has already been declared
}
fn(1);
```
---
### 🟩 `const` 的特性
### 🔸 不可重新賦值
```javascript
const b = 0;
b = 1; // ❌ Assignment to constant variable.
```
---
### 🔸 物件參考可變更屬性,但不可重新指派整個物件
```javascript
const a = {
name: "卡斯伯",
};
a.name = "Ray"; // ✅
a = {
name: "Ray",
}; // ❌ Assignment to constant variable
```
---
## ✅ 建議使用方式
> 💡 **能用 `const` 就用 `const`,其次用 `let`,避免使用 `var`。**

---
## 📍 主題 2:JavaScript — Template Literals(模板字串)
### 🔹 為什麼需要模板字串?
傳統字串拼接方式使用 `+` 號,當內容複雜或需換行時,程式碼可讀性變差:
```javascript
const name = "卡斯伯";
const greeting = "哈囉,我是 " + name + "!";
console.log(greeting); // 輸出:哈囉,我是 卡斯伯!
```
---
### 🔹 使用 Template Literals 的語法
使用反引號( ` )來定義字串,可內嵌變數與表達式:
```javascript
const name = "卡斯伯";
const greeting = `哈囉,我是 ${name}!`;
console.log(greeting); // 輸出:哈囉,我是 卡斯伯!
```
---
### 🔹 多行字串支援
使用模板字串可以自然地換行,無需 `\n` 或字串拼接:
```javascript
const message = `這是第一行
這是第二行`;
console.log(message);
```
✅ 輸出:
```
這是第一行
這是第二行
```
---
### 🔹 可嵌入表達式
Template Literals 最強大的功能之一,就是大括號${}中可以放置**任何有效的 JavaScript 表達式。** 這意味著,只要是能產生一個值得程式碼片段,都可以被嵌入
- **數學片段**
```javascript
const a = 5;
const b = 10;
console.log('家總結果是:${a + b}'); // 輸出:加總結果是15
```
- **函式呼叫**
```javascript
function greet(name){
return '哈囉,${name}!';
}
console.log(greet("666")); // 輸出:哈囉,666!
```
- **方法呼叫**
```javascript
const product = { name: "手機", price: 5000};
console.log('商品名稱:${product.name.toUpperCase()}'); // 輸出:商品名稱:手機
```
- **條件 (三元) 運算子**
```javascript
const score = 85;
const result = '你的成績是 ${score >= 60 ? "及格" : "不及格"}';
console.log(result); // 輸出:你的成績是 及格
```
- **IIFE (立即執行函式)**
```javascript
const quantity = 5;
const unitPrice = 10;
const orderSummary = `總價:${(() => {
const total = quantity * unitPrice;
return total > 50 ? `${total} 元 (免運)` : `${total} 元 (加運費)`;
})()}`;
console.log(orderSummary); // 輸出:總價:50 元 (免運)
```
### 🔹 進階用法:標籤模板 (Tagged Templates)
這是 Template Literals 最靈活的進階用法。在反引號前放置一個函式名稱,這個函式就會被稱為「標籤函式」。它會接收模板字串的靜態部分 (strings 陣列) 和所有嵌入的表達式值 (values 陣列),讓您可以對這些內容進行自定義的解析、處理或轉換,例如過濾、格式化或安全性檢查。
- **範例:將所有數字加粗**
```javascript
// 標籤函式:將所有數字值加上粗體標籤
function emphasizeNumbers(strings, ...values) {
// 使用 reduce 方法遍歷 strings 陣列,並逐步構建最終的字串
return strings.reduce((result, currentString, i) => {
const value = values[i]; // 取得當前字串片段後對應的變數值
// 將當前靜態字串 (currentString) 加入結果中
// 然後判斷 value 的類型:
// 如果 value 是數字 (typeof value === 'number'):
// 則將其用 <strong> HTML 標籤包起來,使其在顯示時加粗。
// 如果 value 不是數字:
// 直接使用 value 的值。
// (value || ''):這個部分確保如果 value 是 undefined 或 null,
// 它會被替換成空字串,避免顯示 'undefined' 或 'null'。
return result + currentString + (typeof value === 'number' ? `<strong>${value}</strong>` : (value || ''));
}, ''); // reduce 的初始值為空字串
}
const product = "筆記本";
const price = 1500;
const taxRate = 0.05;
// 使用標籤模板:在 Template Literal 前面加上 emphasizeNumbers 函式名稱
const finalMessage = emphasizeNumbers`商品:${product},原價:${price}元,含稅價:${price * (1 + taxRate)}元。`;
// 輸出結果
console.log(finalMessage);
// 預期輸出:
// "商品:筆記本,原價:<strong>1500</strong>元,含稅價:<strong>1575</strong>元。"
```
:::spoiler 不會的地方 需求解
1. (strings, ...values)
2. return strings.reduce((result, currentString, i) => { ... }, '')
3. (value || '')
:::
---
## ✅ 建議使用方式
> 💡 在任何需要將變數或複雜邏輯嵌入字串、或者需要多行字串的場景,請務必優先使用 Template Literals(`反引號`)語法。它能顯著提升您程式碼的可讀性、可維護性與開發效率。**
---
## 📍 主題 3:JavaScript — Arrow Functions(箭頭函式)
### 🔹 為什麼需要箭頭函式?
傳統函式定義方式(function expression 或 function declaration)在處理 `this` 關鍵字時行為複雜,尤其是在回呼函式(callback functions)或巢狀函式中,常常需要使用 `bind()` 或將 `this` 賦值給其他變數來維持正確的上下文。此外,語法相對冗長。
```javascript
// 傳統函式範例:this 指向問題
const person = {
name: "小明",
greet: function() {
setTimeout(function() {
// 在這裡,this 不再指向 person 物件,而是指向 setTimeout 的全局或 undefined
console.log("哈囉,我是 " + this.name + "!"); // 輸出:哈囉,我是 undefined!
}, 100);
}
};
person.greet();
```
---
### 🔹 使用 Arrow Functions 的語法
箭頭函式提供更簡潔的語法,並且 **沒有自己的 `this`、`arguments`、`super` 或 `new.target` 綁定**。它的 `this` 值會繼承自定義它的外層作用域(詞法作用域)。
```javascript
// 簡潔語法與 this 綁定
const name = "卡斯伯";
const sayHello = () => `哈囉,我是 ${name}!`;
console.log(sayHello()); // 輸出:哈囉,我是 卡斯伯!
// 解決 this 指向問題的範例
const person = {
name: "小明",
greet: function() {
setTimeout(() => {
// 箭頭函式的 this 繼承自外層 greet 方法的 this,即 person 物件
console.log(`哈囉,我是 ${this.name}!`); // 輸出:哈囉,我是 小明!
}, 100);
}
};
person.greet();
```
---
### 🔹 多種寫法與簡寫
#### ✅ 無參數:
```javascript
const sayHi = () => "嗨!";
console.log(sayHi()); // 輸出:嗨!
```
#### ✅ 單一參數(可省略小括號):
```javascript
const double = num => num * 2;
console.log(double(5)); // 輸出:10
```
#### ✅ 多個參數(需使用小括號):
```javascript
const add = (a, b) => a + b;
console.log(add(3, 7)); // 輸出:10
```
#### ✅ 單行函式體(隱含 return):
```javascript
const square = x => x * x;
console.log(square(4)); // 輸出:16
```
#### ✅ 多行函式體(需使用 `{}` 和 `return`):
```javascript
const calculateSum = (a, b) => {
const sum = a + b;
return `總和是:${sum}`;
};
console.log(calculateSum(10, 20)); // 輸出:總和是:30
```
#### ✅ 返回物件字面量(用括號包裹):
```javascript
const createUser = (name, age) => ({ name: name, age: age });
console.log(createUser("小華", 25)); // 輸出:{ name: '小華', age: 25 }
```
---
### 🔹 進階用法
箭頭函式不僅語法簡潔,其獨特的 `this` 綁定機制使其在許多現代 JavaScript 模式中成為首選。
#### ✅ 作為高階函式的回呼:
```javascript
const numbers = [1, 2, 3, 4, 5];
// 使用箭頭函式配合 map 轉換陣列
const squaredNumbers = numbers.map(num => num * num);
console.log(squaredNumbers); // 輸出:[1, 4, 9, 16, 25]
// 使用箭頭函式配合 filter 過濾陣列
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // 輸出:[2, 4]
```
#### ✅ 結合 Promise / Async-Await:
```javascript
// 假設有一個非同步函式
function fetchData(id) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Data for ID: ${id}`);
}, 500);
});
}
const processData = async () => {
try {
const result1 = await fetchData(1);
console.log(result1); // 輸出:Data for ID: 1
const result2 = await fetchData(2);
console.log(result2); // 輸出:Data for ID: 2
} catch (error) {
console.error(error);
}
};
processData();
```
#### ✅ 在類別(Classes)中的應用:
```javascript
class MyComponent {
constructor() {
this.value = "Hello World";
}
// 使用箭頭函式作為類別屬性
handleClick = () => {
console.log(this.value);
};
// 傳統方法需手動綁定 this(略)
}
const comp = new MyComponent();
comp.handleClick(); // 輸出:Hello World
```
---
### 🔹 不適合使用 Arrow Functions 的情境
#### ⚠️ 物件方法需要獨立 `this`:
```javascript
const counter = {
count: 0,
increment: function() { // 必須是傳統函式
this.count++;
console.log(this.count);
}
};
counter.increment(); // 輸出:1
```
#### ⚠️ 建構函式(Constructor):
```javascript
// 正確:
function Person(name) {
this.name = name;
}
const p = new Person("小李");
// 錯誤:箭頭函式不能作為建構函式
// const ArrowPerson = (name) => { this.name = name; };
// const p2 = new ArrowPerson("小王"); // ❌ TypeError: not a constructor
```
#### ⚠️ 需要 `arguments` 的函式:
```javascript
function logArgsTraditional() {
console.log(arguments); // [1, 2, 3]
}
logArgsTraditional(1, 2, 3);
const logArgsArrow = (...args) => {
console.log(args); // [4, 5, 6]
};
logArgsArrow(4, 5, 6);
// ❌ 錯誤:arguments is not defined
// const logArgsArrowNoArgs = () => { console.log(arguments); };
```
---
## ✅ 建議使用方式
> 💡 **在以下情境中優先使用箭頭函式:**
>
> - 作為回呼函式:如 `setTimeout`、`map`、`filter` 等,避免 `this` 混亂。
> - 需要簡潔語法的簡單函式:單行表達式、快速運算。
> - 不需要獨立的 `this` 綁定時:使用詞法作用域的 `this` 更直觀。
📌 **沒有絕對的最佳函式類型,請根據實際情境選擇最適合的寫法。**
---