JS 扩展运算符详解

请解释和说明下 Javascript 的 扩展运算符 (…)


很久不写代码了,扩展运算符都不知道还有这个东西。

可以用扩展运算符来对对象进行展开。

你好, honeymoose!

很高兴为你解释和说明 JavaScript 的扩展运算符 (...)。这是一个在现代 JavaScript (ES6 及之后版本) 中非常强大且常用的特性。

简单来说,它的核心作用就像是 “解包” 或者 “展开” 一个可迭代对象(比如数组、字符串或对象)。

一、核心思想:打开盒子,拿出内容

你可以把它想象成一个装满物品的盒子。扩展运算符 ... 就是把这个盒子打开,然后把里面的 每一件物品 都单独拿出来,平铺在桌子上。

// 盒子 (数组)
const box = ['苹果', '香蕉', '橙子'];

// 打开盒子,把里面的东西拿出来
console.log(...box);
// 输出: '苹果', '香蕉', '橙子' (注意,这里不再是一个数组,而是三个独立的字符串)

现在我们来看看它在不同场景下的具体用法和强大之处。


二、主要用途和代码示例

1. 在数组中的应用

这是扩展运算符最常见的用途。

a) 合并数组(替代 concat()

以前我们要合并数组,需要使用 concat() 方法。现在用扩展运算符,代码更直观、更简洁。

const fruits = ['🍎', '🍌'];
const vegetables = ['🥦', '🥕'];

// ES5 写法
const food1 = fruits.concat(vegetables);
// -> ['🍎', '🍌', '🥦', '🥕']

// ES6+ 扩展运算符写法
const food2 = [...fruits, ...vegetables];
// -> ['🍎', '🍌', '🥦', '🥕']

b) 复制数组(创建新数组)

这是一个非常重要的用途,尤其是在需要保持数据 不可变性 (Immutability) 的场景中(例如在 React 或 Vue 中更新状态)。

const originalArr = [1, 2, 3];

// 错误的方式:这只是复制了引用,不是创建新数组
// const badCopy = originalArr;
// badCopy.push(4); // originalArr 也会被改变!

// 正确的方式:使用扩展运算符创建了一个全新的、内容相同的数组
const newArr = [...originalArr];
newArr.push(4);

console.log(originalArr); // -> [1, 2, 3] (原始数组没有被修改)
console.log(newArr);      // -> [1, 2, 3, 4]

c) 在数组中插入元素

可以非常方便地在数组的开头、中间或结尾添加新元素来构造一个新数组。

const original = ['b', 'c', 'd'];

// 在开头添加
const atStart = ['a', ...original]; // -> ['a', 'b', 'c', 'd']

// 在结尾添加
const atEnd = [...original, 'e']; // -> ['b', 'c', 'd', 'e']

// 在中间添加
const atMiddle = ['a', ...original, 'e']; // -> ['a', 'b', 'c', 'd', 'e']

2. 在函数调用中的应用

可以将一个数组的元素“展开”,作为函数的参数一个个传入。

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [10, 20, 30];

// ES5 写法
const result1 = sum.apply(null, numbers); // -> 60

// ES6+ 扩展运算符写法
const result2 = sum(...numbers); // 等同于 sum(10, 20, 30)
// -> 60

这比使用 apply() 方法要优雅和易读得多。

3. 在对象中的应用 (ES2018)

这个特性被添加到标准后,极大地简化了对象的操作。

a) 合并对象(替代 Object.assign()

const user = { name: 'Alice', age: 25 };
const profile = { job: 'Developer', city: 'New York' };

// ES6 写法
const userProfile1 = Object.assign({}, user, profile);

// ES2018+ 扩展运算符写法
const userProfile2 = { ...user, ...profile };
// -> { name: 'Alice', age: 25, job: 'Developer', city: 'New York' }

// 如果有同名属性,后面的会覆盖前面的
const updatedUser = { ...user, age: 26 };
// -> { name: 'Alice', age: 26 }

b) 复制对象

和数组一样,这也是实现对象不可变性的关键。

const originalObj = { a: 1, b: 2 };
const clonedObj = { ...originalObj };

clonedObj.c = 3;

console.log(originalObj); // -> { a: 1, b: 2 } (未被改变)
console.log(clonedObj);   // -> { a: 1, b: 2, c: 3 }

4. 其他应用

a) 字符串转为数组

可以轻松地将字符串拆分为单个字符组成的数组。

const greeting = "Hello";
const chars = [...greeting];
// -> ['H', 'e', 'l', 'l', 'o']

b) 与 Set 结合去重

Set 数据结构只存储唯一值,结合扩展运算符可以快速实现数组去重。

const numbersWithDuplicates = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbersWithDuplicates)];
// -> [1, 2, 3, 4, 5]

三、一个重要的“亲戚”:剩余参数 (Rest Parameters)

剩余参数也使用 ... 语法,但它的作用和扩展运算符 完全相反

  • 扩展运算符 (Spread): 展开 数组/对象,将一个整体拆分为多个独立的个体。
  • 剩余参数 (Rest): 收集 多个独立的参数,将它们合并成一个数组。

它通常用在函数定义中。

// ...args 就是剩余参数,它会把传入的所有参数收集到一个名为 args 的数组中
function collectItems(...args) {
  console.log(args); // args 是一个真正的数组
}

collectItems('apple', 'banana', 'cherry');
// 输出: ['apple', 'banana', 'cherry']

// 也可以收集剩余的参数
function processUser(id, ...details) {
  console.log('User ID:', id);
  console.log('Details:', details); // details 收集了 id 之后的所有参数
}

processUser('u001', 'Alice', 25, 'Developer');
// 输出:
// User ID: u001
// Details: ['Alice', 25, 'Developer']
特性 扩展运算符 (Spread Operator) 剩余参数 (Rest Parameter)
作用 将数组或对象 展开 成独立的元素 将多个独立的元素 收集 成一个数组
位置 函数调用、数组字面量、对象字面量中 函数参数定义、解构赋值中
例子 myFunction(...myArray); function myFunction(...args) {}

四、注意事项:浅拷贝 (Shallow Copy)

无论是用于数组还是对象,扩展运算符执行的都是 浅拷贝

这意味着它只会复制第一层的值。如果你的数据结构有嵌套的数组或对象,它只会复制嵌套结构的 引用,而不是创建一个全新的嵌套结构。

const deepData = {
  user: 'Bob',
  details: {
    age: 30,
    city: 'London'
  }
};

const shallowCopy = { ...deepData };

// 修改顶层属性,不会影响原始数据
shallowCopy.user = 'Charlie';
console.log(deepData.user); // -> 'Bob'

// 修改嵌套对象的属性,会影响原始数据!
shallowCopy.details.city = 'Paris';
console.log(deepData.details.city); // -> 'Paris' (原始数据也被改变了!)

如果需要深拷贝,你需要使用其他方法,比如 JSON.parse(JSON.stringify(obj)) (有局限性) 或者专门的库如 lodash.cloneDeep

总结

  • 扩展运算符 ... 是一个用于 展开 可迭代对象的强大工具。
  • 它极大地简化了数组和对象的合并、复制等操作,让代码更简洁、更具可读性。
  • 它是实现数据 不可变性 的重要手段,在现代前端框架中非常关键。
  • 要和功能相反的 剩余参数 ... 区分开。
  • 记住它执行的是 浅拷贝

希望这个解释对你有帮助!掌握它对于编写简洁、高效、现代的 JavaScript 代码至关重要。