icon
剩余参数&展示运算符

剩余参数&展开运算符

剩余参数(Rest Parameters)和展开运算符(Spread Operator)是 ES6 引入的语法特性,使用相同的 ... 语法,但作用相反。

剩余参数(Rest Parameters)

剩余参数允许将不定数量的参数表示为数组。

function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3));        // 6
console.log(sum(1, 2, 3, 4, 5));  // 15

特点:

  • 必须是最后一个参数
  • 是一个真正的数组,可以使用数组方法
// 正确:剩余参数在最后
function fn(a, b, ...rest) {
  console.log(a, b, rest);
}

// 错误:剩余参数不能在中间
// function fn(...rest, a) { } // SyntaxError

// 剩余参数是数组
function logArgs(...args) {
  console.log(Array.isArray(args)); // true
  args.forEach(arg => console.log(arg));
}

展开运算符(Spread Operator)

展开运算符可以将数组或对象展开为多个元素。

  1. 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// 在数组中间展开
const arr3 = [0, ...arr1, 4];
console.log(arr3); // [0, 1, 2, 3, 4]

// 复制数组
const copy = [...arr1];
console.log(copy); // [1, 2, 3]
  1. 函数调用展开
function sum(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

// 与普通参数混合
console.log(sum(0, ...numbers)); // 6 (0, 1, 2)
  1. 对象展开
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 copy = { ...obj1 };
console.log(copy); // { a: 1, b: 2 }

// 覆盖属性
const updated = { ...obj1, b: 3 };
console.log(updated); // { a: 1, b: 3 }

实际应用

  1. 函数参数处理
// 处理不定参数
function formatMessage(template, ...values) {
  return template.replace(/{(\d+)}/g, (match, index) => {
    return values[index] || match;
  });
}

console.log(formatMessage('Hello {0}, you have {1} messages', 'Alice', 5));
// 'Hello Alice, you have 5 messages'

// 参数默认值
function createUser(name, age = 18, ...hobbies) {
  return {
    name,
    age,
    hobbies
  };
}

const user = createUser('Alice', 25, 'reading', 'coding', 'music');
console.log(user);
// { name: 'Alice', age: 25, hobbies: ['reading', 'coding', 'music'] }
  1. 数组操作
// 添加元素
const arr = [1, 2, 3];
const newArr = [...arr, 4, 5];
console.log(newArr); // [1, 2, 3, 4, 5]

// 插入元素
const inserted = [...arr.slice(0, 1), 1.5, ...arr.slice(1)];
console.log(inserted); // [1, 1.5, 2, 3]

// 删除元素(配合 filter)
const removed = [...arr.filter(x => x !== 2)];
console.log(removed); // [1, 3]

// 数组去重
const duplicates = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(duplicates)];
console.log(unique); // [1, 2, 3]
  1. 对象操作
// 添加属性
const user = { name: 'Alice' };
const updated = { ...user, age: 25 };
console.log(updated); // { name: 'Alice', age: 25 }

// 更新属性
const user2 = { name: 'Alice', age: 25 };
const updated2 = { ...user2, age: 26 };
console.log(updated2); // { name: 'Alice', age: 26 }

// 删除属性(配合解构)
const { age, ...withoutAge } = { name: 'Alice', age: 25, city: 'NYC' };
console.log(withoutAge); // { name: 'Alice', city: 'NYC' }

// 合并对象(后面的覆盖前面的)
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
const config = { ...defaults, ...userPrefs };
console.log(config); // { theme: 'dark', lang: 'en' }
  1. 函数式编程
// 组合函数
function compose(...fns) {
  return function(value) {
    return fns.reduceRight((acc, fn) => fn(acc), value);
  };
}

const addOne = x => x + 1;
const multiplyTwo = x => x * 2;
const square = x => x * x;

const composed = compose(square, multiplyTwo, addOne);
console.log(composed(3)); // ((3 + 1) * 2) ^ 2 = 64

// 使用展开运算符调用
const result = compose(...[square, multiplyTwo, addOne])(3);
console.log(result); // 64
  1. 类数组转换
// 将类数组转换为数组
function test() {
  const args = [...arguments];
  console.log(Array.isArray(args)); // true
}

test(1, 2, 3);

// NodeList 转数组
const divs = document.querySelectorAll('div');
const divArray = [...divs];
console.log(Array.isArray(divArray)); // true
  1. 浅拷贝
// 数组浅拷贝
const original = [1, 2, { a: 3 }];
const copy = [...original];
copy[0] = 10;
copy[2].a = 30;
console.log(original); // [1, 2, { a: 30 }] (对象是引用)

// 对象浅拷贝
const obj = { a: 1, b: { c: 2 } };
const objCopy = { ...obj };
objCopy.a = 10;
objCopy.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } } (嵌套对象是引用)
  1. 条件展开
// 条件添加属性
const condition = true;
const obj = {
  name: 'Alice',
  ...(condition && { age: 25 })
};
console.log(obj); // { name: 'Alice', age: 25 }

// 条件添加数组元素
const includeExtra = true;
const arr = [
  1, 2, 3,
  ...(includeExtra ? [4, 5] : [])
];
console.log(arr); // [1, 2, 3, 4, 5]
  1. 解构配合使用
// 剩余元素
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first);  // 1
console.log(second); // 2
console.log(rest);   // [3, 4, 5]

// 剩余属性
const { name, ...others } = { name: 'Alice', age: 25, city: 'NYC' };
console.log(name);   // 'Alice'
console.log(others); // { age: 25, city: 'NYC' }

注意事项

  1. 展开运算符是浅拷贝
const nested = { a: { b: 1 } };
const copy = { ...nested };
copy.a.b = 2;
console.log(nested.a.b); // 2 (被修改了)
  1. 只能展开可迭代对象
// 数组和字符串可以展开
console.log(...[1, 2, 3]);     // 1 2 3
console.log(...'hello');       // h e l l o

// 对象不能直接展开(ES2018+ 可以)
const obj = { a: 1, b: 2 };
// console.log(...obj);        // TypeError (ES2017)
console.log({ ...obj });       // { a: 1, b: 2 } (ES2018+)
  1. 剩余参数必须是最后一个
// 正确
function fn(a, ...rest) { }

// 错误
// function fn(...rest, a) { } // SyntaxError
  1. 性能考虑
// 展开大数组可能影响性能
const largeArray = new Array(1000000).fill(0);
// const spread = [...largeArray]; // 可能较慢

常见模式

  1. 函数重载
function createElement(tag, props, ...children) {
  return {
    tag,
    props: props || {},
    children
  };
}

const element = createElement('div', { id: 'app' }, 'Hello', 'World');
  1. 参数转发
function wrapper(...args) {
  console.log('Before');
  originalFunction(...args);
  console.log('After');
}
  1. 配置合并
function createConfig(userConfig) {
  const defaultConfig = {
    timeout: 1000,
    retries: 3
  };
  return { ...defaultConfig, ...userConfig };
}