扩展语法 (...)

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

¥The spread (...) syntax allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

扩展语法看起来与剩余语法完全相同。在某种程度上,扩展语法与剩余语法相反。Spread 语法将数组 "expands" 放入其元素中,而剩余语法则收集多个元素并将它们 "condenses" 放入单个元素中。参见 其余参数剩余属性

¥Spread syntax looks exactly like rest syntax. In a way, spread syntax is the opposite of rest syntax. Spread syntax "expands" an array into its elements, while rest syntax collects multiple elements and "condenses" them into a single element. See rest parameters and rest property.

Try it

语法

¥Syntax

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

描述

¥Description

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

¥Spread syntax can be used when all elements from an object or array need to be included in a new array or object, or should be applied one-by-one in a function call's arguments list. There are three distinct places that accept the spread syntax:

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

¥Although the syntax looks the same, they come with slightly different semantics.

只有 iterable 值(如 ArrayString)可以分布在 数组文字 和参数列表中。许多对象是不可迭代的,包括所有缺少 Symbol.iterator 方法的 普通对象

¥Only iterable values, like Array and String, can be spread in array literals and argument lists. Many objects are not iterable, including all plain objects that lack a Symbol.iterator method:

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

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

¥On the other hand, spreading in object literals enumerates the own properties of the value. For typical arrays, all indices are enumerable own properties, so arrays can be spread into objects.

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

所有 primitives 都可以分布在对象中。只有字符串具有可枚举的自己的属性,并且传播任何其他内容不会在新对象上创建属性。

¥All primitives can be spread in objects. Only strings have enumerable own properties, and spreading anything else doesn't create properties on the new object.

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

使用扩展语法进行函数调用时,请注意可能会超出 JavaScript 引擎的参数长度限制。详细信息请参见 Function.prototype.apply()

¥When using spread syntax for function calls, be aware of the possibility of exceeding the JavaScript engine's argument length limit. See Function.prototype.apply() for more details.

示例

¥Examples

在函数调用中传播

¥Spread in function calls

替换 apply()

¥Replace apply()

当你想要使用数组的元素作为函数的参数时,通常使用 Function.prototype.apply()

¥It is common to use Function.prototype.apply() in cases where you want to use the elements of an array as arguments to a function.

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

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

¥With spread syntax the above can be written as:

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

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

¥Any argument in the argument list can use spread syntax, and the spread syntax can be used multiple times.

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

应用新的运算符

¥Apply for new operator

当使用 new 调用构造函数时,不可能直接使用数组和 apply(),因为 apply() 调用目标函数而不是构造它,这意味着 new.target 将是 undefined。然而,由于扩展语法,数组可以轻松地与 new 一起使用:

¥When calling a constructor with new, it's not possible to directly use an array and apply(), because apply() calls the target function instead of constructing it, which means, among other things, that new.target will be undefined. However, an array can be easily used with new thanks to spread syntax:

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

以数组文字形式传播

¥Spread in array literals

更强大的数组文字

¥A more powerful array literal

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

¥Without spread syntax, the array literal syntax is no longer sufficient to create a new array using an existing array as one part of it. Instead, imperative code must be used using a combination of methods, including push(), splice(), concat(), etc. With spread syntax, this becomes much more succinct:

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

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

¥Just like spread for argument lists, ... can be used anywhere in the array literal, and may be used more than once.

复制数组

¥Copying an array

你可以使用扩展语法来创建数组的 shallow copy。每个数组元素都保留其标识而不被复制。

¥You can use spread syntax to make a shallow copy of an array. Each array element retains its identity without getting copied.

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() 允许深度复制某些 支持的类型 的值。详细信息请参见 浅拷贝

¥Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays. The same is true with Object.assign() — no native operation in JavaScript does a deep clone. The web API method structuredClone() allows deep copying values of certain supported types. See shallow copy for more details.

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

连接数组的更好方法

¥A better way to concatenate arrays

Array.prototype.concat() 通常用于将数组连接到现有数组的末尾。如果没有扩展语法,则执行如下:

¥Array.prototype.concat() is often used to concatenate an array to the end of an existing array. Without spread syntax, this is done as:

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

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

使用扩展语法,这将变为:

¥With spread syntax this becomes:

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() 通常用于在现有数组的开头插入值数组。如果没有扩展语法,则执行如下:

¥Array.prototype.unshift() is often used to insert an array of values at the start of an existing array. Without spread syntax, this is done as:

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]

使用扩展语法,这将变为:

¥With spread syntax, this becomes:

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

¥Note: Unlike unshift(), this creates a new arr1, instead of modifying the original arr1 array in-place.

有条件地将值添加到数组

¥Conditionally adding values to an array

你可以根据条件使用 条件运算符. 使数组文字中存在或不存在某个元素。

¥You can make an element present or absent in an array literal, depending on a condition, using a conditional operator.

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

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

¥When the condition is false, we spread an empty array, so that nothing gets added to the final array. Note that this is different from the following:

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

这样的话,当 isSummerfalse 时,会额外添加一个 undefined 元素,这个元素会被 Array.prototype.map() 等方法访问到。

¥In this case, an extra undefined element is added when isSummer is false, and this element will be visited by methods such as Array.prototype.map().

在对象字面量中传播

¥Spread in object literals

复制和合并对象

¥Copying and merging objects

你可以使用展开语法将多个对象合并为一个新对象。

¥You can use spread syntax to merge multiple objects into one new object.

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 }

单个展开创建原始对象的浅表副本(但没有不可枚举属性并且不复制原型),类似于 复制数组

¥A single spread creates a shallow copy of the original object (but without non-enumerable properties and without copying the prototype), similar to copying an array.

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

重写属性

¥Overriding properties

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

¥When one object is spread into another object, or when multiple objects are spread into one object, and properties with identical names are encountered, the property takes the last value assigned while remaining in the position it was originally set.

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 }

有条件地向对象添加属性

¥Conditionally adding properties to an object

你可以根据条件使用 条件运算符. 使对象文本中存在或不存在某个元素。

¥You can make an element present or absent in an object literal, depending on a condition, using a conditional operator.

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

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

¥The case where the condition is false is an empty object, so that nothing gets spread into the final object. Note that this is different from the following:

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

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

¥In this case, the watermelon property is always present and will be visited by methods such as Object.keys().

因为基元也可以扩展到对象中,并且根据观察,所有 falsy 值都不具有可枚举属性,因此你可以简单地使用 逻辑与 运算符:

¥Because primitives can be spread into objects as well, and from the observation that all falsy values do not have enumerable properties, you can simply use a logical AND operator:

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

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

¥In this case, if isSummer is any falsy value, no property will be created on the fruits object.

与 Object.assign() 比较

¥Comparing with Object.assign()

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

¥Note that Object.assign() can be used to mutate an object, whereas spread syntax can't.

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

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

¥In addition, Object.assign() triggers setters on the target object, whereas spread syntax does not.

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

¥You cannot naively re-implement the Object.assign() function through a single spreading:

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

¥In the above example, the spread syntax does not work as one might expect: it spreads an array of arguments into the object literal, due to the rest parameter. Here is an implementation of merge using the spread syntax, whose behavior is similar to Object.assign(), except that it doesn't trigger setters, nor mutates any object:

js
const obj1 = { foo: "bar", x: 42 };
const obj2 = { foo: "baz", y: 13 };
const merge = (...objects) =>
  objects.reduce((acc, cur) => ({ ...acc, ...cur }));

const mergedObj1 = merge(obj1, obj2);
// { foo: 'baz', x: 42, y: 13 }

规范

Specification
ECMAScript Language Specification
# prod-SpreadElement
ECMAScript Language Specification
# prod-ArgumentList
ECMAScript Language Specification
# prod-PropertyDefinition

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看