JavaScript 基礎:ES6+ 現代語法總整理
各位好!
這篇是 JavaScript 系列的第十二單元,也是最後一個單元。我們會總結所有 ES6 之後引入的現代語法特性。
ES6(ES2015)
let 和 const
// var 的問題
var x = 1;
var x = 2; // 可以重複宣告
console.log(x); // 2
if (true) {
var y = 3;
}
console.log(y); // 3(沒有區塊作用域)
// let:區塊作用域、不可重複宣告
let a = 1;
// let a = 2; // 錯誤
if (true) {
let b = 3;
}
// console.log(b); // 錯誤:b is not defined
// const:不可重新賦值
const PI = 3.14;
// PI = 3.14159; // 錯誤
// 但物件和陣列的內容可以修改
const obj = { name: "Alice" };
obj.name = "Bob"; // 可以
// obj = {}; // 錯誤
const arr = [1, 2, 3];
arr.push(4); // 可以
// arr = []; // 錯誤
箭頭函式
// 傳統函式
function add(a, b) {
return a + b;
}
// 箭頭函式
const add = (a, b) => {
return a + b;
};
// 簡化版(單行自動回傳)
const add = (a, b) => a + b;
// 單一參數可省略括號
const square = x => x * x;
// 無參數需要空括號
const greet = () => "Hello";
// 回傳物件需要加括號
const createUser = (name, age) => ({ name, age });
// this 的差異
const obj = {
name: "Alice",
// 傳統函式的 this 指向 obj
greet1: function() {
console.log(this.name);
},
// 箭頭函式的 this 指向外層
greet2: () => {
console.log(this.name); // undefined
}
};
模板字串
// 傳統字串拼接
const name = "Alice";
const age = 25;
const message = "My name is " + name + ", I'm " + age + " years old.";
// 模板字串
const message = `My name is ${name}, I'm ${age} years old.`;
// 多行字串
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// 表達式
const price = 100;
const quantity = 3;
const total = `Total: $${price * quantity}`;
// 函式呼叫
const upperName = `Name: ${name.toUpperCase()}`;
// 巢狀模板
const nested = `Outer ${`Inner ${name}`}`;
解構賦值
// 陣列解構
const numbers = [1, 2, 3, 4, 5];
const [first, second] = numbers;
console.log(first, second); // 1 2
// 跳過元素
const [a, , c] = numbers; // 1 3
// 剩餘元素
const [x, y, ...rest] = numbers;
console.log(rest); // [3, 4, 5]
// 預設值
const [p = 0, q = 0] = [1];
console.log(p, q); // 1 0
// 交換變數
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
// 物件解構
const user = {
name: "Alice",
age: 25,
email: "[email protected]"
};
const { name, age } = user;
console.log(name, age); // Alice 25
// 重新命名
const { name: userName, age: userAge } = user;
// 預設值
const { phone = "未提供" } = user;
// 巢狀解構
const data = {
user: {
name: "Bob",
address: {
city: "Taipei"
}
}
};
const { user: { name, address: { city } } } = data;
// 函式參數解構
function printUser({ name, age = 0 }) {
console.log(`${name}, ${age} 歲`);
}
printUser({ name: "Charlie", age: 30 });
預設參數
// ES5
function greet(name, greeting) {
greeting = greeting || "Hello";
return greeting + ", " + name;
}
// ES6
function greet(name, greeting = "Hello") {
return `${greeting}, ${name}`;
}
console.log(greet("Alice")); // "Hello, Alice"
console.log(greet("Bob", "Hi")); // "Hi, Bob"
// 預設值可以是表達式
function createId(prefix = "ID", number = Date.now()) {
return `${prefix}-${number}`;
}
// 預設值可以參考前面的參數
function calculate(a, b = a * 2) {
return a + b;
}
console.log(calculate(5)); // 15 (5 + 10)
console.log(calculate(5, 3)); // 8 (5 + 3)
剩餘參數和展開運算子
// 剩餘參數(Rest Parameters)
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// 必須是最後一個參數
function test(first, second, ...rest) {
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
}
test(1, 2, 3, 4, 5);
// 展開運算子(Spread Operator)
// 陣列
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 複製陣列
const original = [1, 2, 3];
const copy = [...original];
// 物件
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
// 複製物件
const user = { name: "Alice", age: 25 };
const userCopy = { ...user };
// 函式呼叫
const numbers = [1, 5, 3, 9, 2];
console.log(Math.max(...numbers)); // 9
簡寫屬性和方法
// 屬性簡寫
const name = "Alice";
const age = 25;
// ES5
const user1 = {
name: name,
age: age
};
// ES6
const user2 = { name, age };
// 方法簡寫
const calculator = {
// ES5
add: function(a, b) {
return a + b;
},
// ES6
subtract(a, b) {
return a - b;
}
};
// 計算屬性名稱
const key = "username";
const obj = {
[key]: "Alice",
["get" + key.charAt(0).toUpperCase() + key.slice(1)]() {
return this[key];
}
};
console.log(obj.username); // "Alice"
console.log(obj.getUsername()); // "Alice"
類別(Class)
// ES5 建構函式
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
// ES6 類別
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
// Getter
get info() {
return `${this.name}, ${this.age} 歲`;
}
// Setter
set birthYear(year) {
this.age = new Date().getFullYear() - year;
}
// 靜態方法
static create(name, age) {
return new Person(name, age);
}
}
const person = new Person("Alice", 25);
console.log(person.greet());
console.log(person.info);
// 繼承
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 呼叫父類別建構函式
this.grade = grade;
}
greet() {
return `${super.greet()}, I'm a student`;
}
study() {
return `${this.name} is studying`;
}
}
const student = new Student("Bob", 20, "A");
console.log(student.greet());
console.log(student.study());
Promise
// 建立 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("成功");
} else {
reject("失敗");
}
}, 1000);
});
// 使用 Promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("完成"));
// Promise 鏈
fetch("/api/user")
.then(response => response.json())
.then(data => {
console.log(data);
return fetch(`/api/posts/${data.id}`);
})
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));
// Promise.all
Promise.all([
fetch("/api/users"),
fetch("/api/posts"),
fetch("/api/comments")
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([users, posts, comments]) => {
console.log({ users, posts, comments });
});
模組
// 匯出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
// 預設匯出
export default class Calculator {
// ...
}
// 匯入
import Calculator, { PI, add } from "./math.js";
import * as math from "./math.js";
ES2016(ES7)
Array.prototype.includes()
const numbers = [1, 2, 3, 4, 5];
// ES5
console.log(numbers.indexOf(3) !== -1); // true
// ES7
console.log(numbers.includes(3)); // true
console.log(numbers.includes(6)); // false
// 可以搜尋 NaN
console.log([NaN].includes(NaN)); // true
// 第二個參數指定起始位置
console.log(numbers.includes(3, 3)); // false(從索引 3 開始找)
指數運算子
// ES5
console.log(Math.pow(2, 3)); // 8
// ES7
console.log(2 ** 3); // 8
console.log(3 ** 2); // 9
// 組合使用
let x = 2;
x **= 3; // x = x ** 3
console.log(x); // 8
ES2017(ES8)
async/await
// Promise 寫法
function fetchData() {
return fetch("/api/data")
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => console.error(error));
}
// async/await 寫法
async function fetchData() {
try {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error(error);
}
}
// 平行執行
async function fetchMultiple() {
const [users, posts] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]);
return { users, posts };
}
Object.entries() / Object.values()
const user = {
name: "Alice",
age: 25,
city: "Taipei"
};
// Object.keys()(ES5)
console.log(Object.keys(user));
// ["name", "age", "city"]
// Object.values()
console.log(Object.values(user));
// ["Alice", 25, "Taipei"]
// Object.entries()
console.log(Object.entries(user));
// [["name", "Alice"], ["age", 25], ["city", "Taipei"]]
// 遍歷
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// 轉換物件
const doubled = Object.fromEntries(
Object.entries({ a: 1, b: 2 }).map(([k, v]) => [k, v * 2])
);
console.log(doubled); // { a: 2, b: 4 }
String padding
// padStart
console.log("5".padStart(3, "0")); // "005"
console.log("ABC".padStart(10, "X")); // "XXXXXXXABC"
// padEnd
console.log("5".padEnd(3, "0")); // "500"
console.log("ABC".padEnd(10, "X")); // "ABCXXXXXXX"
// 實用範例:格式化日期
const month = "3";
const day = "7";
const formatted = `2024-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
console.log(formatted); // "2024-03-07"
ES2018(ES9)
物件的剩餘/展開屬性
// 剩餘屬性
const user = {
name: "Alice",
age: 25,
email: "[email protected]",
city: "Taipei"
};
const { name, age, ...rest } = user;
console.log(name); // "Alice"
console.log(age); // 25
console.log(rest); // { email: "...", city: "..." }
// 展開屬性
const defaults = { theme: "light", lang: "en" };
const userPrefs = { lang: "zh", fontSize: 14 };
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: "light", lang: "zh", fontSize: 14 }
// 複製物件
const original = { a: 1, b: 2 };
const copy = { ...original };
Promise.finally()
fetch("/api/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => {
console.log("無論成功或失敗都會執行");
// 清理資源、隱藏載入動畫等
});
非同步迭代
// 一般迭代器
const iterable = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
return i < 3 ? { value: i++, done: false } : { done: true };
}
};
}
};
for (const value of iterable) {
console.log(value); // 0, 1, 2
}
// 非同步迭代器
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
async next() {
await new Promise(resolve => setTimeout(resolve, 1000));
return i < 3 ? { value: i++, done: false } : { done: true };
}
};
}
};
(async () => {
for await (const value of asyncIterable) {
console.log(value); // 0, 1, 2(每秒一個)
}
})();
ES2019(ES10)
Array.prototype.flat() / flatMap()
// flat() - 扁平化陣列
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(nested.flat(Infinity)); // 完全扁平化
// flatMap() - map 後扁平化
const numbers = [1, 2, 3];
console.log(numbers.map(x => [x, x * 2]));
// [[1, 2], [2, 4], [3, 6]]
console.log(numbers.flatMap(x => [x, x * 2]));
// [1, 2, 2, 4, 3, 6]
// 實用範例:拆分句子成單字
const sentences = ["Hello World", "JavaScript is fun"];
const words = sentences.flatMap(s => s.split(" "));
console.log(words);
// ["Hello", "World", "JavaScript", "is", "fun"]
Object.fromEntries()
// 陣列轉物件
const entries = [["name", "Alice"], ["age", 25]];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: "Alice", age: 25 }
// Map 轉物件
const map = new Map([
["name", "Bob"],
["age", 30]
]);
const obj2 = Object.fromEntries(map);
// 實用範例:過濾物件屬性
const user = { name: "Alice", age: 25, password: "secret" };
const publicUser = Object.fromEntries(
Object.entries(user).filter(([key]) => key !== "password")
);
console.log(publicUser); // { name: "Alice", age: 25 }
String.prototype.trimStart() / trimEnd()
const str = " Hello World ";
console.log(str.trim()); // "Hello World"
console.log(str.trimStart()); // "Hello World "
console.log(str.trimEnd()); // " Hello World"
// 別名
console.log(str.trimLeft()); // 同 trimStart
console.log(str.trimRight()); // 同 trimEnd
ES2020(ES11)
Optional Chaining
const user = {
name: "Alice",
address: {
city: "Taipei"
}
};
// 傳統寫法
const city = user && user.address && user.address.city;
// Optional Chaining
const city = user?.address?.city;
console.log(city); // "Taipei"
// 不存在時回傳 undefined
const zip = user?.address?.zip;
console.log(zip); // undefined
// 陣列
const firstItem = arr?.[0];
// 函式
const result = obj.method?.();
// 實用範例
function getCity(user) {
return user?.address?.city ?? "未知";
}
Nullish Coalescing
// || 運算子的問題
const value1 = 0 || "default";
console.log(value1); // "default"(0 被視為 falsy)
const value2 = "" || "default";
console.log(value2); // "default"(空字串被視為 falsy)
// ?? 運算子只處理 null 和 undefined
const value3 = 0 ?? "default";
console.log(value3); // 0
const value4 = "" ?? "default";
console.log(value4); // ""
const value5 = null ?? "default";
console.log(value5); // "default"
const value6 = undefined ?? "default";
console.log(value6); // "default"
// 實用範例
function createUser(options = {}) {
return {
name: options.name ?? "匿名",
age: options.age ?? 0,
active: options.active ?? true
};
}
BigInt
// Number 的限制
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
// BigInt
const big1 = 9007199254740991n;
const big2 = BigInt("9007199254740991");
// 運算
console.log(big1 + 1n); // 9007199254740992n
console.log(big1 * 2n); // 18014398509481982n
// 不能混用 BigInt 和 Number
// console.log(big1 + 1); // 錯誤
// 轉換
console.log(Number(big1)); // 9007199254740991
console.log(big1.toString()); // "9007199254740991"
Promise.allSettled()
const promises = [
Promise.resolve(1),
Promise.reject("錯誤"),
Promise.resolve(3)
];
// Promise.all() - 有一個失敗就全部失敗
Promise.all(promises)
.then(results => console.log(results))
.catch(error => console.log(error)); // "錯誤"
// Promise.allSettled() - 等待全部完成
Promise.allSettled(promises)
.then(results => {
console.log(results);
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: "錯誤" },
// { status: "fulfilled", value: 3 }
// ]
});
globalThis
// 統一的全域物件
// 瀏覽器:window
// Node.js:global
// Web Workers:self
// ES2020:globalThis 在所有環境都指向全域物件
console.log(globalThis);
// 實用範例
function getGlobalObject() {
return globalThis;
}
ES2021(ES12)
String.prototype.replaceAll()
const str = "Hello World, Hello JavaScript";
// replace() 只替換第一個
console.log(str.replace("Hello", "Hi"));
// "Hi World, Hello JavaScript"
// replaceAll() 替換所有
console.log(str.replaceAll("Hello", "Hi"));
// "Hi World, Hi JavaScript"
// 正則表達式
console.log(str.replaceAll(/Hello/g, "Hi"));
Promise.any()
const p1 = new Promise((resolve, reject) =>
setTimeout(() => reject("錯誤 1"), 100)
);
const p2 = new Promise((resolve, reject) =>
setTimeout(() => resolve("成功"), 200)
);
const p3 = new Promise((resolve, reject) =>
setTimeout(() => reject("錯誤 2"), 300)
);
// 回傳第一個成功的
Promise.any([p1, p2, p3])
.then(result => console.log(result)) // "成功"
.catch(error => console.log(error));
// 如果全部失敗
Promise.any([
Promise.reject("錯誤 1"),
Promise.reject("錯誤 2")
])
.catch(error => {
console.log(error); // AggregateError
console.log(error.errors); // ["錯誤 1", "錯誤 2"]
});
邏輯賦值運算子
// &&=
let x = 1;
x &&= 2; // x = x && 2
console.log(x); // 2
// ||=
let y = 0;
y ||= 5; // y = y || 5
console.log(y); // 5
// ??=
let z = null;
z ??= 10; // z = z ?? 10
console.log(z); // 10
// 實用範例
const options = {};
options.timeout ??= 5000; // 只在 undefined/null 時設定預設值
數字分隔符號
// 提高可讀性
const billion = 1_000_000_000;
const bytes = 0b1111_0000_1010_0101;
const hex = 0xFF_EC_DE_5E;
const price = 1_234_567.89;
console.log(billion); // 1000000000
console.log(bytes); // 61605
ES2022(ES13)
Top-level await
// 模組頂層可以使用 await
const data = await fetch("/api/data").then(r => r.json());
console.log(data);
// 條件式匯入
const module = await import(`./locale/${lang}.js`);
// 不需要包在 async 函式裡
Class Fields
class Counter {
// 公開欄位
count = 0;
// 私有欄位
#privateCount = 0;
// 私有方法
#increment() {
this.#privateCount++;
}
// 靜態公開欄位
static description = "計數器";
// 靜態私有欄位
static #instances = 0;
constructor() {
Counter.#instances++;
}
getPrivateCount() {
return this.#privateCount;
}
}
const counter = new Counter();
console.log(counter.count); // 0
// console.log(counter.#privateCount); // 錯誤:私有欄位
at() 方法
const arr = [1, 2, 3, 4, 5];
// 傳統方式取最後一個元素
console.log(arr[arr.length - 1]); // 5
// at() 方法
console.log(arr.at(-1)); // 5(最後一個)
console.log(arr.at(-2)); // 4(倒數第二個)
console.log(arr.at(0)); // 1(第一個)
// 字串也支援
const str = "Hello";
console.log(str.at(-1)); // "o"
實用總結
現代 JavaScript 最佳實踐
// 1. 使用 const 和 let
const PI = 3.14159;
let count = 0;
// 2. 箭頭函式
const add = (a, b) => a + b;
// 3. 模板字串
const message = `Hello, ${name}!`;
// 4. 解構
const { name, age } = user;
const [first, second] = array;
// 5. 展開運算子
const newArray = [...oldArray, newItem];
const newObject = { ...oldObject, newProp: value };
// 6. 簡寫屬性
const obj = { name, age };
// 7. async/await
async function fetchData() {
const data = await fetch("/api").then(r => r.json());
return data;
}
// 8. Optional Chaining
const city = user?.address?.city;
// 9. Nullish Coalescing
const value = input ?? defaultValue;
// 10. Array 方法
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
小結
這篇我們總結了:
- ES6 的核心特性(let/const、箭頭函式、類別等)
- ES2016-ES2022 的新增功能
- 現代 JavaScript 最佳實踐
恭喜你!
完成整個 JavaScript 系列後,你已經掌握:
- JavaScript 基礎語法
- 變數、資料型別、運算子
- 函式與陣列操作
- 物件與迴圈
- 作用域與閉包
- 非同步程式設計
- 模組系統
- DOM 操作
- 所有現代 ES6+ 語法
接下來可以:
- 學習 Next.js 系列 進入網頁開發
- 學習更多前端框架(React、Vue)
- 練習實作專案累積經驗
- 學習 TypeScript 增強型別安全
祝你學習順利!
0 留言
發表留言