ES6(ECMAScript 2015)是 JavaScript 歷史上最大的一次更新,之後每年也持續加入新功能,這些統稱為 ES6+。前面幾篇我們已經用過其中一部分,例如 let/const、箭頭函式、樣板字面值與 async/await。這一篇要介紹另外幾個最實用的現代語法:解構、展開運算子、物件簡寫、可選串連、模組等,讓你的程式碼更簡潔、更好讀。
一、解構賦值(Destructuring)
解構可以把陣列或物件中的值,一次拆出來指定給多個變數。
1. 陣列解構
const colors = ["紅", "綠", "藍"];
// 舊寫法
// const first = colors[0];
// const second = colors[1];
// 解構寫法
const [first, second, third] = colors;
console.log(first, second, third); // 紅 綠 藍
// 略過不需要的項目
const [, , last] = colors;
console.log(last); // 藍
// 交換兩個變數的值
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
2. 物件解構
const student = { name: "小明", age: 20, city: "台北" };
const { name, age } = student;
console.log(name, age); // 小明 20
// 改名稱:把 city 取出並命名為 location
const { city: location } = student;
console.log(location); // 台北
// 設定預設值
const { score = 60 } = student;
console.log(score); // 60(student 沒有 score,使用預設值)
3. 函式參數解構
這是實務上最常見的用法,特別適合參數很多的函式:
function showUser({ name, age = 18 }) {
console.log(`${name},${age} 歲`);
}
showUser({ name: "小華", age: 25 }); // 小華,25 歲
showUser({ name: "小美" }); // 小美,18 歲
二、展開運算子與其餘參數(...)
三個點 ... 在不同位置有不同意思:放在「使用端」是展開,放在「接收端」是收集(其餘參數)。
1. 展開陣列與物件
// 合併陣列
const arr1 = [1, 2, 3];
const arr2 = [4, 5];
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5]
// 複製陣列(淺層複製)
const copy = [...arr1];
// 合併物件,後面的會覆蓋前面相同的屬性
const defaults = { theme: "light", lang: "zh-TW" };
const custom = { theme: "dark" };
const settings = { ...defaults, ...custom };
console.log(settings); // { theme: "dark", lang: "zh-TW" }
// 更新物件中的某個屬性,同時保留原物件不變
const user = { name: "小明", age: 20 };
const olderUser = { ...user, age: 21 };
console.log(user.age, olderUser.age); // 20 21
2. 其餘參數
// 把剩下的參數收集成陣列
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// 搭配解構使用
const [head, ...rest] = [10, 20, 30, 40];
console.log(head); // 10
console.log(rest); // [20, 30, 40]
提醒:展開運算子做的是「淺層複製」,如果物件裡還有巢狀的物件或陣列,內層仍然是共用同一份參照。
三、物件的簡寫語法
const name = "小明";
const age = 20;
// 屬性簡寫:名稱與變數相同時可省略
const student = { name, age };
// 方法簡寫
const obj = {
sayHi() {
console.log("Hi!");
}
};
// 計算屬性名稱:用 [] 動態決定屬性名
const key = "score";
const data = { [key]: 95, [`${key}Level`]: "A" };
console.log(data); // { score: 95, scoreLevel: "A" }
四、可選串連(?.)與空值合併(??)
讀取深層屬性時,只要中間有一層是 null 或 undefined,程式就會出錯。這兩個運算子就是為了解決這類問題:
const user = { name: "小明", address: null };
// 沒有可選串連:會出現 TypeError
// console.log(user.address.city);
// 有可選串連:遇到 null/undefined 就停止,回傳 undefined
console.log(user.address?.city); // undefined
// 也可用於陣列與函式呼叫
console.log(user.hobbies?.[0]); // undefined
console.log(user.sayHi?.()); // undefined(不會出錯)
// 空值合併 ??:只有左邊是 null 或 undefined 時,才使用右邊的值
const city = user.address?.city ?? "未填寫";
console.log(city); // 未填寫
?? 與 || 的差異
const count = 0;
console.log(count || 10); // 10(|| 會把 0、""、false 都當成「假值」)
console.log(count ?? 10); // 0 (?? 只在 null / undefined 時才取右邊)
當 0、空字串、false 是有效的值時,請使用 ??,才不會誤判。
五、實用的陣列與字串新方法
const nums = [5, 12, 8, 130, 44];
console.log(nums.includes(8)); // true
console.log(nums.find(n => n > 10)); // 12(第一個符合的)
console.log(nums.findIndex(n => n > 10)); // 1
console.log(nums.some(n => n > 100)); // true(只要有一個符合)
console.log(nums.every(n => n > 1)); // true(全部都符合)
// 攤平巢狀陣列
console.log([1, [2, [3]]].flat(2)); // [1, 2, 3]
// 字串方法
console.log("Hello".padStart(8, "*")); // ***Hello
console.log(" JS ".trim()); // JS
console.log("a-b-c".replaceAll("-", "+")); // a+b+c
// 物件轉換
const obj = { a: 1, b: 2 };
console.log(Object.keys(obj)); // ["a", "b"]
console.log(Object.values(obj)); // [1, 2]
console.log(Object.entries(obj)); // [["a", 1], ["b", 2]]
六、模組(Modules)
當程式越寫越大,把所有程式碼放在同一個檔案會很難維護。模組讓你把功能拆成多個檔案,再用 export 匯出、import 匯入。
math.js(匯出)
// 具名匯出(可以有多個)
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
// 預設匯出(每個檔案只能有一個)
export default function multiply(a, b) {
return a * b;
}
main.js(匯入)
// 預設匯出不用大括號;具名匯出要用大括號
import multiply, { PI, add } from "./math.js";
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
console.log(PI); // 3.14159
index.html(載入模組)
<script type="module" src="main.js"></script>
注意:瀏覽器基於安全性,不允許直接用 file:// 開啟的網頁載入模組,必須透過本機伺服器執行。最簡單的方式是在 VS Code 安裝「Live Server」擴充功能,或在資料夾中執行 npx serve。
七、綜合範例:購物車小計
把本篇的多個語法組合起來,在 Console 中就可以直接執行:
const cart = [
{ name: "鍵盤", price: 1200, qty: 1 },
{ name: "滑鼠", price: 600, qty: 2 },
{ name: "隨身碟", price: 300, qty: 3, discount: 50 }
];
// 計算每個品項的小計(解構 + 預設值 + 箭頭函式)
const withSubtotal = cart.map(({ name, price, qty, discount = 0 }) => ({
name,
subtotal: (price - discount) * qty
}));
console.log(withSubtotal);
// 加總(reduce + 解構)
const total = withSubtotal.reduce((sum, { subtotal }) => sum + subtotal, 0);
console.log(`總金額:${total} 元`);
// 新增品項,但不修改原陣列(展開運算子)
const newCart = [...cart, { name: "螢幕", price: 4500, qty: 1 }];
console.log(cart.length, newCart.length); // 3 4
// 安全讀取可能不存在的資料(?. 與 ??)
const coupon = undefined;
console.log(coupon?.code ?? "無優惠碼");
八、練習題
- 使用解構,從
{ title: "JS 入門", author: { name: "小明" } }一次取出title與author.name。(提示:巢狀解構) - 寫一個函式
max(...nums),使用其餘參數回傳最大值。 - 使用展開運算子,寫出「新增一位學生到陣列、且不改動原陣列」的程式。
- 把第二篇的待辦清單改寫成兩個模組:
storage.js負責資料處理,main.js負責畫面。
結語
解構、展開運算子、可選串連與模組,都是現在的前端專案(React、Vue 等框架)幾乎天天會用到的語法。只要多寫幾次,就會發現程式碼變得更短、更清楚。到這裡,這個 JavaScript 入門系列的四個主題就告一段落了:基礎語法、DOM 與事件、非同步、ES6+。接下來可以挑一個小專案(例如天氣查詢、記帳本)把這些技巧整合起來練習。如有任何問題,歡迎在下方留言討論!
沒有留言:
張貼留言