展开语法 (...)
扩展 (...) 语法允许在预期有零个或多个参数(用于函数调用)或元素(用于数组字面量)的位置展开可迭代对象,例如数组或字符串。在对象字面量中,扩展语法枚举对象的属性,并将键值对添加到正在创建的对象中。
扩展语法看起来与剩余语法完全相同。从某种程度上说,扩展语法是剩余语法的反向操作。扩展语法将数组“展开”为它的元素,而剩余语法则收集多个元素并将其“压缩”成单个元素。请参阅剩余参数和剩余属性。
试一试
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));
// Expected output: 6
console.log(sum.apply(null, numbers));
// Expected output: 6
语法
myFunction(a, ...iterableObj, b)
[1, ...iterableObj, '4', 'five', 6]
{ ...obj, key: 'value' }
描述
当需要将对象或数组中的所有元素包含在新数组或对象中,或者应在函数调用的参数列表中逐个应用时,可以使用扩展语法。有三个不同的地方接受扩展语法:
- 函数参数列表 (
myFunction(a, ...iterableObj, b)) - 数组字面量 (
[1, ...iterableObj, '4', 'five', 6]) - 对象字面量 (
{ ...obj, key: 'value' })
尽管语法看起来相同,但它们的语义略有不同。
只有可迭代值,如Array和String,才能在数组字面量和参数列表中进行扩展。许多对象是不可迭代的,包括所有缺少Symbol.iterator方法的普通对象。
const obj = { key1: "value1" };
const array = [...obj]; // TypeError: obj is not iterable
另一方面,在对象字面量中进行扩展会枚举值的自身属性。对于典型的数组,所有索引都是可枚举的自身属性,因此数组可以扩展到对象中。
const array = [1, 2, 3];
const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }
所有原始值都可以在对象中展开。只有字符串具有可枚举的自身属性,展开其他任何值都不会在新对象上创建属性。
const obj = { ...true, ..."test", ...10 };
// { '0': 't', '1': 'e', '2': 's', '3': 't' }
当在函数调用中使用扩展语法时,请注意可能会超出 JavaScript 引擎的参数长度限制。有关更多详细信息,请参阅Function.prototype.apply()。
示例
函数调用中的扩展
替换 apply()
在需要将数组的元素用作函数参数的情况下,通常会使用Function.prototype.apply()。
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction.apply(null, args);
使用扩展语法,上述内容可以写成:
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction(...args);
参数列表中的任何参数都可以使用扩展语法,并且扩展语法可以使用多次。
function myFunction(v, w, x, y, z) {}
const args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);
应用于 new 操作符
当使用new调用构造函数时,无法直接使用数组和apply(),因为apply()是调用目标函数而不是构造它,这意味着,除其他事项外,new.target将是undefined。但是,借助扩展语法,数组可以轻松地与new一起使用:
const dateFields = [1970, 0, 1]; // 1 Jan 1970
const d = new Date(...dateFields);
数组字面量中的扩展
更强大的数组字面量
如果没有扩展语法,数组字面量语法不足以创建一个新数组,其中包含现有数组的一部分。相反,必须使用命令式代码,结合使用多种方法,包括push()、splice()、concat()等。有了扩展语法,这会变得更加简洁:
const parts = ["shoulders", "knees"];
const lyrics = ["head", ...parts, "and", "toes"];
// ["head", "shoulders", "knees", "and", "toes"]
就像参数列表的扩展一样,...可以在数组字面量中的任何位置使用,并且可以使用多次。
复制数组
您可以使用扩展语法对数组进行浅复制。每个数组元素都保留其身份而不会被复制。
const arr = [1, 2, 3];
const arr2 = [...arr]; // like arr.slice()
arr2.push(4);
// arr2 becomes [1, 2, 3, 4]
// arr remains unaffected
扩展语法在复制数组时实际上只深入一层。因此,它可能不适合复制多维数组。Object.assign()也是如此——JavaScript 中没有原生的操作可以进行深克隆。Web API 方法structuredClone()允许深复制某些支持类型的值。有关更多详细信息,请参阅浅复制。
const a = [[1], [2], [3]];
const b = [...a];
b.shift().shift();
// 1
// Oh no! Now array 'a' is affected as well:
console.log(a);
// [[], [2], [3]]
连接数组的更好方法
Array.prototype.concat()通常用于将数组连接到现有数组的末尾。如果没有扩展语法,则按如下方式完成:
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
// Append all items from arr2 onto arr1
arr1 = arr1.concat(arr2);
有了扩展语法,这变为:
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2];
// arr1 is now [0, 1, 2, 3, 4, 5]
Array.prototype.unshift()通常用于在现有数组的开头插入一个值数组。如果没有扩展语法,则按如下方式完成:
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
// Prepend all items from arr2 onto arr1
Array.prototype.unshift.apply(arr1, arr2);
console.log(arr1); // [3, 4, 5, 0, 1, 2]
有了扩展语法,这变为:
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1];
console.log(arr1); // [3, 4, 5, 0, 1, 2]
注意:与unshift()不同,这会创建一个新的arr1,而不是就地修改原始arr1数组。
有条件地向数组添加值
您可以使用条件运算符,根据条件使元素在数组字面量中存在或不存在。
const isSummer = false;
const fruits = ["apple", "banana", ...(isSummer ? ["watermelon"] : [])];
// ['apple', 'banana']
当条件为false时,我们展开一个空数组,这样最终数组中就不会添加任何内容。请注意,这与以下情况不同:
const fruits = ["apple", "banana", isSummer ? "watermelon" : undefined];
// ['apple', 'banana', undefined]
在这种情况下,当isSummer为false时,会添加一个额外的undefined元素,并且该元素将被Array.prototype.map()等方法访问。
对象字面量中的扩展
复制和合并对象
您可以使用扩展语法将多个对象合并到一个新对象中。
const obj1 = { foo: "bar", x: 42 };
const obj2 = { bar: "baz", y: 13 };
const mergedObj = { ...obj1, ...obj2 };
// { foo: "bar", x: 42, bar: "baz", y: 13 }
单个扩展会创建原始对象的浅层副本(但不包括不可枚举的属性,也不复制原型),类似于复制数组。
const clonedObj = { ...obj1 };
// { foo: "bar", x: 42 }
覆盖属性
当一个对象扩展到另一个对象中,或者当多个对象扩展到一个对象中,并且遇到具有相同名称的属性时,该属性将采用最后赋给的值,同时保留其最初设置的位置。
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const mergedObj = { x: 41, ...obj1, ...obj2, y: 9 }; // { x: 42, foo: "baz", y: 9 }
有条件地向对象添加属性
您可以使用条件运算符,根据条件使元素在对象字面量中存在或不存在。
const isSummer = false;
const fruits = {
apple: 10,
banana: 5,
...(isSummer ? { watermelon: 30 } : {}),
};
// { apple: 10, banana: 5 }
条件为false时的情况是一个空对象,因此没有任何内容会被扩展到最终对象中。请注意,这与以下情况不同:
const fruits = {
apple: 10,
banana: 5,
watermelon: isSummer ? 30 : undefined,
};
// { apple: 10, banana: 5, watermelon: undefined }
在这种情况下,watermelon属性始终存在,并将被Object.keys()等方法访问。
因为原始值也可以扩展到对象中,并且根据所有假值都没有可枚举属性的观察,您可以简单地使用逻辑与运算符:
const isSummer = false;
const fruits = {
apple: 10,
banana: 5,
...(isSummer && { watermelon: 30 }),
};
在这种情况下,如果isSummer是任何假值,则不会在fruits对象上创建任何属性。
与 Object.assign() 比较
请注意,Object.assign()可以用于修改对象,而扩展语法不能。
const obj1 = { foo: "bar", x: 42 };
Object.assign(obj1, { x: 1337 });
console.log(obj1); // { foo: "bar", x: 1337 }
此外,Object.assign()会触发目标对象上的 setter,而扩展语法不会。
const objectAssign = Object.assign(
{
set foo(val) {
console.log(val);
},
},
{ foo: 1 },
);
// Logs "1"; objectAssign.foo is still the original setter
const spread = {
set foo(val) {
console.log(val);
},
...{ foo: 1 },
};
// Nothing is logged; spread.foo is 1
您不能通过一次扩展来简单地重新实现Object.assign()函数:
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const merge = (...objects) => ({ ...objects });
const mergedObj1 = merge(obj1, obj2);
// { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } }
const mergedObj2 = merge({}, obj1, obj2);
// { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } }
在上面的示例中,扩展语法没有按预期工作:由于剩余参数,它将一个参数数组扩展到对象字面量中。下面是使用扩展语法的merge实现,其行为类似于Object.assign(),除了它不触发 setter,也不修改任何对象:
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const merge = (...objects) =>
objects.reduce((acc, cur) => ({ ...acc, ...cur }));
const mergedObj = merge(obj1, obj2);
// { foo: 'baz', x: 42, y: 13 }
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # prod-SpreadElement |
| ECMAScript® 2026 语言规范 # prod-ArgumentList |
| ECMAScript® 2026 语言规范 # prod-PropertyDefinition |
浏览器兼容性
加载中…