JavaScript 中的展开语法(Spread Syntax) ,它也使用三个点(...)作为符号,但其作用与**剩余参数(Rest Parameters)**正好相反。
什么是展开语法?
展开语法允许一个可迭代对象(如数组、字符串、Set、Map 等)或者一个对象(自 ES2018 起)在期望零个或多个参数(用于函数调用)或元素(用于数组字面量)或键值对(用于对象字面量)的位置进行展开。
简单来说,它就是把一个集合(数组、对象等)的内容“摊开”或“铺平”,将其中的元素或属性一个个取出来,放到当前的位置。
语法:
- 对于可迭代对象(数组、字符串等):...iterableExpression
- 对于对象(ES2018+):...objectExpression
主要应用场景:
1. 在数组字面量中 (Array Literals):
-
合并数组 (Concatenating Arrays):
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const arr3 = [0, ...arr1, ...arr2, 7]; // 展开 arr1 和 arr2 的元素 console.log(arr3); // 输出: [0, 1, 2, 3, 4, 5, 6, 7] // 传统方式: // const arr3_old = [0].concat(arr1, arr2, [7]);
-
复制数组 (Shallow Copying Arrays):
创建一个数组的浅拷贝。const originalArray = [1, 2, 3]; const copiedArray = [...originalArray]; console.log(copiedArray); // 输出: [1, 2, 3] console.log(copiedArray === originalArray); // 输出: false (它们是不同的数组实例) copiedArray.push(4); console.log(originalArray); // 输出: [1, 2, 3] (原数组不受影响)
注意: 这是浅拷贝。如果数组元素是对象或其他数组,拷贝的是引用,修改嵌套内容会影响原数组。
const originalNested = [{ a: 1 }, { b: 2 }]; const copiedNested = [...originalNested]; copiedNested[0].a = 99; console.log(originalNested[0].a); // 输出: 99 (原数组的嵌套对象被修改了)
-
向数组中插入元素 (Immutably):
const base = ['a', 'b', 'c']; const itemToAdd = 'x'; // 在末尾添加 const addedEnd = [...base, itemToAdd]; console.log(addedEnd); // 输出: ['a', 'b', 'c', 'x'] // 在开头添加 const addedStart = [itemToAdd, ...base]; console.log(addedStart); // 输出: ['a', 'x', 'b', 'c'] (修正:应为 ['x', 'a', 'b', 'c']) -> Let's correct the thinking, it should be ['x', 'a', 'b', 'c'] console.log(base); // 输出: ['a', 'b', 'c'] (原数组不变)
-
将类数组对象或可迭代对象转换为真数组:
// NodeList (来自 DOM 查询) // const divs = document.querySelectorAll('div'); // const divArray = [...divs]; // 将 NodeList 转换为 Array // arguments 对象 (函数内部,ES6 之前常用) function exampleFunc() { const argsArray = [...arguments]; console.log(argsArray); } exampleFunc(1, 'a', true); // 输出: [1, 'a', true] // Set const mySet = new Set([1, 2, 2, 3]); const setArray = [...mySet]; console.log(setArray); // 输出: [1, 2, 3] // String const str = "hello"; const charArray = [...str]; console.log(charArray); // 输出: ['h', 'e', 'l', 'l', 'o']
2. 在函数调用中 (Function Calls):
将数组的元素作为独立的参数传递给函数。这通常用来替代 Function.prototype.apply()
。
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
// 使用展开语法
const result = sum(...numbers); // 等价于 sum(1, 2, 3)
console.log(result); // 输出: 6
// 传统方式 (使用 apply)
// const resultApply = sum.apply(null, numbers);
// console.log(resultApply); // 输出: 6
// 结合其他参数
function logArgs(arg1, arg2, ...rest) { // 这里 ...rest 是剩余参数
console.log("Arg1:", arg1);
console.log("Arg2:", arg2);
console.log("Rest:", rest);
}
const data = ['a', 'b', 'c', 'd'];
logArgs('fixed1', ...data, 'fixed2'); // 等价于 logArgs('fixed1', 'a', 'b', 'c', 'd', 'fixed2')
// 输出:
// Arg1: fixed1
// Arg2: a
// Rest: [ 'b', 'c', 'd', 'fixed2' ]
-
在对象字面量中 (Object Literals - ES2018+):
将一个对象的自有(own)可枚举(enumerable)属性复制到新的对象字面量中。-
合并对象 (Merging Objects):
const obj1 = { a: 1, b: 2 }; const obj2 = { b: 3, c: 4 }; // obj2 的 b 会覆盖 obj1 的 b const mergedObj = { ...obj1, ...obj2, d: 5 }; console.log(mergedObj); // 输出: { a: 1, b: 3, c: 4, d: 5 } // 顺序很重要,后面的属性会覆盖前面的同名属性 const mergedObjReverse = { ...obj2, ...obj1 }; console.log(mergedObjReverse); // 输出: { b: 2, c: 4, a: 1 }
-
复制对象 (Shallow Copying Objects):
创建一个对象的浅拷贝。const originalObject = { name: "Alice", age: 30 }; const copiedObject = { ...originalObject }; console.log(copiedObject); // 输出: { name: 'Alice', age: 30 } console.log(copiedObject === originalObject); // 输出: false copiedObject.age = 31; console.log(originalObject.age); // 输出: 30 (原对象不受影响)
注意: 同样是浅拷贝。嵌套对象属性依然是引用。
-
向对象中添加或更新属性 (Immutably):
const user = { id: 101, name: "Bob" }; const updatedUser = { ...user, name: "Charlie", city: "New York" }; // 更新 name,添加 city console.log(updatedUser); // 输出: { id: 101, name: 'Charlie', city: 'New York' } console.log(user); // 输出: { id: 101, name: 'Bob' } (原对象不变)
-
展开语法 vs 剩余参数 (Spread vs Rest):
虽然它们都使用 ...
,但用途完全不同:
-
展开语法 (Spread):
- 作用: 将一个集合(数组、对象等)展开成独立的元素/属性。
- 位置: 用在数组字面量
[...]
、函数调用func(...)
、对象字面量{...}
中。 - 目的: “拿出”集合的内容。
-
剩余参数 (Rest):
- 作用: 将多个独立的参数收集到一个数组中。
- 位置: 用在函数定义的参数列表 function(...rest) 或解构赋值
const [first, ...rest] = arr
; 中。 - 目的: “聚合”多个项。
示例对比:
// 展开语法 (Spread)
const nums = [1, 2, 3];
console.log(...nums); // 输出: 1 2 3 (在 console.log 调用中展开)
const moreNums = [0, ...nums, 4]; // 输出: [0, 1, 2, 3, 4] (在数组字面量中展开)
// 剩余参数 (Rest)
function collectArgs(...args) { // 收集所有传入的参数到一个名为 args 的数组中
console.log(args);
}
collectArgs(1, 2, 3); // 输出: [1, 2, 3]
const [first, ...remaining] = [10, 20, 30, 40]; // 解构赋值中的剩余参数
console.log(first); // 输出: 10
console.log(remaining); // 输出: [20, 30, 40]
总结:
展开语法是 ES6 (对象展开是 ES2018) 引入的一个非常实用和简洁的特性,它使得数组和对象的复制、合并以及函数参数传递变得更加直观和方便,是现代 JavaScript 开发中不可或缺的一部分。