在ECMAScript規範中,省略號(...)主要有兩個重要用途:展開語法(Spread syntax) 和剩餘參數(Rest parameters)

  • 淺拷貝:使用擴展運算符 ...(最簡潔)
  • 深拷貝:使用 JSON.parse(JSON.stringify()) 或 Lodash 的 _.cloneDeep()

快速概覽

語法類型

作用

使用場景

展開語法

將數組/對象展開為單個元素

函數調用、數組字面量、對象字面量

剩餘參數

將多個元素收集為數組/對象

函數參數、數組解構、對象解構

詳細解析

1. 展開語法 (Spread Syntax)

數組展開
// 基本用法
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];
console.log(arr2); // [1, 2, 3, 4, 5, 6]

// 函數參數傳遞
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3

// 複製數組(淺拷貝)
const original = [1, 2, 3];
const copy = [...original];
對象展開
// 合併對象
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }

// 對象淺拷貝
const originalObj = { name: "John", age: 30 };
const copyObj = { ...originalObj };

// 覆蓋屬性
const base = { color: "red", size: "large" };
const updated = { ...base, color: "blue" };
console.log(updated); // { color: "blue", size: "large" }

2. 剩餘參數 (Rest Parameters)

函數參數
// 收集剩餘參數為數組
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

// 與其他參數結合使用
function greet(greeting, ...names) {
    return names.map(name => `${greeting}, ${name}!`);
}
console.log(greet("Hello", "Alice", "Bob", "Charlie"));
// ["Hello, Alice!", "Hello, Bob!", "Hello, Charlie!"]
數組解構
// 提取前幾個元素,剩餘元素收集到新數組
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]

// 跳過元素
const [head, , ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [3, 4, 5]
對象解構
// 提取特定屬性,剩餘屬性收集到新對象
const person = { name: "Alice", age: 25, city: "New York", country: "USA" };
const { name, age, ...address } = person;
console.log(name); // "Alice"
console.log(age); // 25
console.log(address); // { city: "New York", country: "USA" }

實際應用場景

1. 函數參數處理

// 替代 arguments 對象
function logMessages(...messages) {
    messages.forEach(message => console.log(message));
}
logMessages("Error:", "File not found", "Code: 404");

// 靈活的配置函數
function createElement(tag, { className, id, ...attrs } = {}) {
    const element = document.createElement(tag);
    if (className) element.className = className;
    if (id) element.id = id;
    
    // 設置其他屬性
    Object.keys(attrs).forEach(key => {
        element.setAttribute(key, attrs[key]);
    });
    
    return element;
}

2. 數據轉換和處理

// 數組合並和去重
const arr1 = [1, 2, 3];
const arr2 = [2, 3, 4];
const mergedUnique = [...new Set([...arr1, ...arr2])];
console.log(mergedUnique); // [1, 2, 3, 4]

// 對象屬性過濾
const user = { 
    name: "John", 
    password: "secret", 
    email: "john@example.com",
    age: 30
};

// 移除敏感信息
const { password, ...publicProfile } = user;
console.log(publicProfile);
// { name: "John", email: "john@example.com", age: 30 }

3. React中的應用

// Props傳遞
function Button({ variant, size, ...props }) {
    return (
        <button 
            className={`btn btn-${variant} btn-${size}`}
            {...props}  // 傳遞所有其他props(onClick, disabled等)
        />
    );
}

// 狀態更新
const [state, setState] = useState({ count: 0, theme: 'dark' });

function updateTheme(newTheme) {
    setState(prevState => ({
        ...prevState,  // 保留其他狀態
        theme: newTheme
    }));
}

注意事項

1. 淺拷貝問題

const nestedArray = [[1], [2], [3]];
const shallowCopy = [...nestedArray];
shallowCopy[0].push(99);
console.log(nestedArray[0]); // [1, 99] - 原數組也被修改了

const nestedObj = { user: { name: "John" } };
const shallowObjCopy = { ...nestedObj };
shallowObjCopy.user.name = "Jane";
console.log(nestedObj.user.name); // "Jane" - 原對象也被修改了

2. 使用限制

// 剩餘元素必須是最後一個
// ❌ 錯誤寫法
// const [...rest, last] = [1, 2, 3, 4];

// ✅ 正確寫法
const [first, ...rest] = [1, 2, 3, 4];

// 對象中只能有一個剩餘參數
// ❌ 錯誤寫法
// const { ...rest, ...more } = { a: 1, b: 2 };

3. 瀏覽器兼容性

// 檢查支持情況
const supportsSpread = () => {
    try {
        eval("[...[1,2,3]]");
        return true;
    } catch (e) {
        return false;
    }
};

console.log("Spread syntax supported:", supportsSpread());

總結

ES規範中的省略號提供了強大而靈活的數據處理能力:

  • 展開語法:用於"展開"可迭代對象,適合數據合併、函數調用等場景
  • 剩餘參數:用於"收集"多個元素,適合參數處理、解構賦值等場景

這兩種語法大大簡化了JavaScript代碼,提高了開發效率和代碼可讀性。