展开语法 (...)

Baseline 已广泛支持

此功能已成熟,并可在许多设备和浏览器版本上运行。自 2015 年 10 月以来,它已在所有浏览器中可用。

扩展 (...) 语法允许在预期有零个或多个参数(用于函数调用)或元素(用于数组字面量)的位置展开可迭代对象,例如数组或字符串。在对象字面量中,扩展语法枚举对象的属性,并将键值对添加到正在创建的对象中。

扩展语法看起来与剩余语法完全相同。从某种程度上说,扩展语法是剩余语法的反向操作。扩展语法将数组“展开”为它的元素,而剩余语法则收集多个元素并将其“压缩”成单个元素。请参阅剩余参数剩余属性

试一试

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

语法

js
myFunction(a, ...iterableObj, b)
[1, ...iterableObj, '4', 'five', 6]
{ ...obj, key: 'value' }

描述

当需要将对象或数组中的所有元素包含在新数组或对象中,或者应在函数调用的参数列表中逐个应用时,可以使用扩展语法。有三个不同的地方接受扩展语法:

尽管语法看起来相同,但它们的语义略有不同。

只有可迭代值,如ArrayString,才能在数组字面量和参数列表中进行扩展。许多对象是不可迭代的,包括所有缺少Symbol.iterator方法的普通对象

js
const obj = { key1: "value1" };
const array = [...obj]; // TypeError: obj is not iterable

另一方面,在对象字面量中进行扩展会枚举值的自身属性。对于典型的数组,所有索引都是可枚举的自身属性,因此数组可以扩展到对象中。

js
const array = [1, 2, 3];
const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }

所有原始值都可以在对象中展开。只有字符串具有可枚举的自身属性,展开其他任何值都不会在新对象上创建属性。

js
const obj = { ...true, ..."test", ...10 };
// { '0': 't', '1': 'e', '2': 's', '3': 't' }

当在函数调用中使用扩展语法时,请注意可能会超出 JavaScript 引擎的参数长度限制。有关更多详细信息,请参阅Function.prototype.apply()

示例

函数调用中的扩展

替换 apply()

在需要将数组的元素用作函数参数的情况下,通常会使用Function.prototype.apply()

js
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction.apply(null, args);

使用扩展语法,上述内容可以写成:

js
function myFunction(x, y, z) {}
const args = [0, 1, 2];
myFunction(...args);

参数列表中的任何参数都可以使用扩展语法,并且扩展语法可以使用多次。

js
function myFunction(v, w, x, y, z) {}
const args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);

应用于 new 操作符

当使用new调用构造函数时,无法直接使用数组和apply(),因为apply()调用目标函数而不是构造它,这意味着,除其他事项外,new.target将是undefined。但是,借助扩展语法,数组可以轻松地与new一起使用:

js
const dateFields = [1970, 0, 1]; // 1 Jan 1970
const d = new Date(...dateFields);

数组字面量中的扩展

更强大的数组字面量

如果没有扩展语法,数组字面量语法不足以创建一个新数组,其中包含现有数组的一部分。相反,必须使用命令式代码,结合使用多种方法,包括push()splice()concat()等。有了扩展语法,这会变得更加简洁:

js
const parts = ["shoulders", "knees"];
const lyrics = ["head", ...parts, "and", "toes"];
//  ["head", "shoulders", "knees", "and", "toes"]

就像参数列表的扩展一样,...可以在数组字面量中的任何位置使用,并且可以使用多次。

复制数组

您可以使用扩展语法对数组进行浅复制。每个数组元素都保留其身份而不会被复制。

js
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()允许深复制某些支持类型的值。有关更多详细信息,请参阅浅复制

js
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()通常用于将数组连接到现有数组的末尾。如果没有扩展语法,则按如下方式完成:

js
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];

// Append all items from arr2 onto arr1
arr1 = arr1.concat(arr2);

有了扩展语法,这变为:

js
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()通常用于在现有数组的开头插入一个值数组。如果没有扩展语法,则按如下方式完成:

js
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]

有了扩展语法,这变为:

js
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数组。

有条件地向数组添加值

您可以使用条件运算符,根据条件使元素在数组字面量中存在或不存在。

js
const isSummer = false;
const fruits = ["apple", "banana", ...(isSummer ? ["watermelon"] : [])];
// ['apple', 'banana']

当条件为false时,我们展开一个空数组,这样最终数组中就不会添加任何内容。请注意,这与以下情况不同:

js
const fruits = ["apple", "banana", isSummer ? "watermelon" : undefined];
// ['apple', 'banana', undefined]

在这种情况下,当isSummerfalse时,会添加一个额外的undefined元素,并且该元素将被Array.prototype.map()等方法访问。

对象字面量中的扩展

复制和合并对象

您可以使用扩展语法将多个对象合并到一个新对象中。

js
const obj1 = { foo: "bar", x: 42 };
const obj2 = { bar: "baz", y: 13 };

const mergedObj = { ...obj1, ...obj2 };
// { foo: "bar", x: 42, bar: "baz", y: 13 }

单个扩展会创建原始对象的浅层副本(但不包括不可枚举的属性,也不复制原型),类似于复制数组

js
const clonedObj = { ...obj1 };
// { foo: "bar", x: 42 }

覆盖属性

当一个对象扩展到另一个对象中,或者当多个对象扩展到一个对象中,并且遇到具有相同名称的属性时,该属性将采用最后赋给的值,同时保留其最初设置的位置。

js
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 }

有条件地向对象添加属性

您可以使用条件运算符,根据条件使元素在对象字面量中存在或不存在。

js
const isSummer = false;
const fruits = {
  apple: 10,
  banana: 5,
  ...(isSummer ? { watermelon: 30 } : {}),
};
// { apple: 10, banana: 5 }

条件为false时的情况是一个空对象,因此没有任何内容会被扩展到最终对象中。请注意,这与以下情况不同:

js
const fruits = {
  apple: 10,
  banana: 5,
  watermelon: isSummer ? 30 : undefined,
};
// { apple: 10, banana: 5, watermelon: undefined }

在这种情况下,watermelon属性始终存在,并将被Object.keys()等方法访问。

因为原始值也可以扩展到对象中,并且根据所有假值都没有可枚举属性的观察,您可以简单地使用逻辑与运算符:

js
const isSummer = false;
const fruits = {
  apple: 10,
  banana: 5,
  ...(isSummer && { watermelon: 30 }),
};

在这种情况下,如果isSummer是任何假值,则不会在fruits对象上创建任何属性。

与 Object.assign() 比较

请注意,Object.assign()可以用于修改对象,而扩展语法不能。

js
const obj1 = { foo: "bar", x: 42 };
Object.assign(obj1, { x: 1337 });
console.log(obj1); // { foo: "bar", x: 1337 }

此外,Object.assign()会触发目标对象上的 setter,而扩展语法不会。

js
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()函数:

js
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,也不修改任何对象:

js
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

浏览器兼容性

另见