解构赋值

解构赋值语法是 JavaScript 表达式,它允许将数组中的值或对象中的属性解包到不同的变量中。

试一试

语法

js
const [a, b] = array;
const [a, , b] = array;
const [a = aDefault, b] = array;
const [a, b, ...rest] = array;
const [a, , b, ...rest] = array;
const [a, b, ...{ pop, push }] = array;
const [a, b, ...[c, d]] = array;

const { a, b } = obj;
const { a: a1, b: b1 } = obj;
const { a: a1 = aDefault, b = bDefault } = obj;
const { a, b, ...rest } = obj;
const { a: a1, b: b1, ...rest } = obj;
const { [key]: a } = obj;

let a, b, a1, b1, c, d, rest, pop, push;
[a, b] = array;
[a, , b] = array;
[a = aDefault, b] = array;
[a, b, ...rest] = array;
[a, , b, ...rest] = array;
[a, b, ...{ pop, push }] = array;
[a, b, ...[c, d]] = array;

({ a, b } = obj); // parentheses are required
({ a: a1, b: b1 } = obj);
({ a: a1 = aDefault, b = bDefault } = obj);
({ a, b, ...rest } = obj);
({ a: a1, b: b1, ...rest } = obj);

描述

对象和数组字面量表达式提供了一种简单的方法来创建临时的数据包。

js
const x = [1, 2, 3, 4, 5];

解构赋值使用类似的语法,但将其用于赋值左侧。它定义了要从源变量中解包哪些值。

js
const x = [1, 2, 3, 4, 5];
const [y, z] = x;
console.log(y); // 1
console.log(z); // 2

类似地,您可以在赋值左侧解构对象。

js
const obj = { a: 1, b: 2 };
const { a, b } = obj;
// is equivalent to:
// const a = obj.a;
// const b = obj.b;

此功能类似于 Perl 和 Python 等语言中的功能。

有关特定于数组或对象解构的功能,请参阅下面的各个示例

绑定和赋值

对于对象和数组解构,有两种解构模式:绑定模式赋值模式,它们具有略微不同的语法。

在绑定模式中,模式以声明关键字(varletconst)开头。然后,每个单独的属性必须绑定到一个变量或进一步解构。

js
const obj = { a: 1, b: { c: 2 } };
const {
  a,
  b: { c: d },
} = obj;
// Two variables are bound: `a` and `d`

所有变量共享相同的声明,因此,如果您希望某些变量可重新赋值而其他变量为只读,则可能需要解构两次——一次使用let,一次使用const

js
const obj = { a: 1, b: { c: 2 } };
const { a } = obj; // a is constant
let {
  b: { c: d },
} = obj; // d is re-assignable

在许多其他语言为您绑定变量的语法中,您可以使用绑定解构模式。这些包括

在赋值模式中,模式不以关键字开头。每个解构的属性都分配给赋值目标——该目标可能事先已用varlet声明,或者它是另一个对象的属性——通常,任何可以出现在赋值表达式左侧的内容。

js
const numbers = [];
const obj = { a: 1, b: 2 };
({ a: numbers[0], b: numbers[1] } = obj);
// The properties `a` and `b` are assigned to properties of `numbers`

注意:在不带声明的情况下使用对象字面量解构赋值时,需要在赋值语句周围使用括号( ... )

{ a, b } = { a: 1, b: 2 } 不是有效的独立语法,因为根据表达式语句的规则,左侧的{ a, b }被视为块而不是对象字面量。但是,({ a, b } = { a: 1, b: 2 })有效,const { a, b } = { a: 1, b: 2 }也有效。

如果您的编码风格不包括尾随分号,则( ... )表达式需要以分号开头,否则它可能用于执行上一行的函数。

请注意,上面代码的等效绑定模式不是有效的语法

js
const numbers = [];
const obj = { a: 1, b: 2 };
const { a: numbers[0], b: numbers[1] } = obj;

// This is equivalent to:
//   const numbers[0] = obj.a;
//   const numbers[1] = obj.b;
// Which definitely is not valid.

您只能将赋值模式用作赋值运算符的左侧。您不能将它们与复合赋值运算符(如+=*=)一起使用。

默认值

每个解构的属性都可以有一个默认值。当属性不存在或值为undefined时,将使用默认值。如果属性值为null,则不使用它。

js
const [a = 1] = []; // a is 1
const { b = 2 } = { b: undefined }; // b is 2
const { c = 2 } = { c: null }; // c is null

默认值可以是任何表达式。它只会在必要时进行评估。

js
const { b = console.log("hey") } = { b: 2 };
// Does not log anything, because `b` is defined and there's no need
// to evaluate the default value.

剩余属性

您可以用剩余属性...rest结束解构模式。此模式会将对象或数组的所有剩余属性存储到新的对象或数组中。

js
const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(others); // { b: 2, c: 3 }

const [first, ...others2] = [1, 2, 3];
console.log(others2); // [2, 3]

剩余属性必须是模式中的最后一个,并且不能有尾随逗号。

js
const [a, ...b,] = [1, 2, 3];

// SyntaxError: rest element may not have a trailing comma
// Always consider using rest operator as the last element

示例

数组解构

基本变量赋值

js
const foo = ["one", "two", "three"];

const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"

解构的元素多于源元素

在从右侧赋值指定长度为N的数组进行数组解构时,如果左侧赋值指定的变量数量大于N,则仅为前N个变量赋值。其余变量的值将为 undefined。

js
const foo = ["one", "two"];

const [red, yellow, green, blue] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // undefined
console.log(blue); // undefined

交换变量

两个变量的值可以在一个解构表达式中交换。

在没有解构赋值的情况下,交换两个值需要一个临时变量(或者,在某些低级语言中,XOR 交换技巧)。

js
let a = 1;
let b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

const arr = [1, 2, 3];
[arr[2], arr[1]] = [arr[1], arr[2]];
console.log(arr); // [1, 3, 2]

解析从函数返回的数组

始终可以从函数返回数组。解构可以使处理数组返回值更简洁。

在此示例中,f()将其输出值[1, 2]作为输出,可以使用解构在一行中解析。

js
function f() {
  return [1, 2];
}

const [a, b] = f();
console.log(a); // 1
console.log(b); // 2

忽略一些返回值

您可以忽略您不感兴趣的返回值

js
function f() {
  return [1, 2, 3];
}

const [a, , b] = f();
console.log(a); // 1
console.log(b); // 3

const [c] = f();
console.log(c); // 1

您还可以忽略所有返回值

js
[, ,] = f();

将绑定模式用作剩余属性

数组解构赋值的剩余属性可以是另一个数组或对象绑定模式。内部解构从收集剩余元素后创建的数组中进行解构,因此您无法以这种方式访问原始可迭代对象中的任何属性。

js
const [a, b, ...{ length }] = [1, 2, 3];
console.log(a, b, length); // 1 2 1
js
const [a, b, ...[c, d]] = [1, 2, 3, 4];
console.log(a, b, c, d); // 1 2 3 4

这些绑定模式甚至可以嵌套,只要每个剩余属性都是列表中的最后一个即可。

js
const [a, b, ...[c, d, ...[e, f]]] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c, d, e, f); // 1 2 3 4 5 6

另一方面,对象解构只能有一个标识符作为剩余属性。

js
const { a, ...{ b } } = { a: 1, b: 2 };
// SyntaxError: `...` must be followed by an identifier in declaration contexts

let a, b;
({ a, ...{ b } } = { a: 1, b: 2 });
// SyntaxError: `...` must be followed by an assignable reference in assignment contexts

从正则表达式匹配中解包值

当正则表达式exec()方法找到匹配项时,它会返回一个数组,其中首先包含字符串的整个匹配部分,然后包含与正则表达式中每个带括号的组匹配的字符串部分。解构赋值允许您轻松地将这些部分解包到数组中,如果不需要完整匹配,则可以忽略它。

js
function parseProtocol(url) {
  const parsedURL = /^(\w+):\/\/([^/]+)\/(.*)$/.exec(url);
  if (!parsedURL) {
    return false;
  }
  console.log(parsedURL);
  // ["https://mdn.org.cn/en-US/docs/Web/JavaScript",
  // "https", "developer.mozilla.org", "en-US/docs/Web/JavaScript"]

  const [, protocol, fullhost, fullpath] = parsedURL;
  return protocol;
}

console.log(
  parseProtocol("https://mdn.org.cn/en-US/docs/Web/JavaScript"),
);
// "https"

对任何可迭代对象使用数组解构

数组解构调用右侧的可迭代协议。因此,任何可迭代对象(不一定是数组)都可以被解构。

js
const [a, b] = new Map([
  [1, 2],
  [3, 4],
]);
console.log(a, b); // [1, 2] [3, 4]

不可迭代对象不能作为数组解构。

js
const obj = { 0: "a", 1: "b", length: 2 };
const [a, b] = obj;
// TypeError: obj is not iterable

可迭代对象仅迭代到所有绑定都分配为止。

js
const obj = {
  *[Symbol.iterator]() {
    for (const v of [0, 1, 2, 3]) {
      console.log(v);
      yield v;
    }
  },
};
const [a, b] = obj; // Only logs 0 and 1

剩余绑定被急切地评估并创建一个新数组,而不是使用旧的可迭代对象。

js
const obj = {
  *[Symbol.iterator]() {
    for (const v of [0, 1, 2, 3]) {
      console.log(v);
      yield v;
    }
  },
};
const [a, b, ...rest] = obj; // Logs 0 1 2 3
console.log(rest); // [2, 3] (an array)

对象解构

基本赋值

js
const user = {
  id: 42,
  isVerified: true,
};

const { id, isVerified } = user;

console.log(id); // 42
console.log(isVerified); // true

分配给新的变量名

可以从对象中解包属性并将其分配给与对象属性不同的名称的变量。

js
const o = { p: 42, q: true };
const { p: foo, q: bar } = o;

console.log(foo); // 42
console.log(bar); // true

例如,在这里,const { p: foo } = o从对象o中获取名为p的属性并将其分配给名为foo的局部变量。

分配给新的变量名并提供默认值

一个属性可以同时

  • 从对象中解包并分配给具有不同名称的变量。
  • 如果解包的值为undefined,则分配一个默认值。
js
const { a: aa = 10, b: bb = 5 } = { a: 3 };

console.log(aa); // 3
console.log(bb); // 5

解包作为函数参数传递的对象的属性

传递给函数参数的对象也可以解构为变量,然后可以在函数体中访问这些变量。与对象赋值类似,解构语法允许新变量与原始属性具有相同名称或不同的名称,并为原始对象未定义该属性的情况分配默认值。

考虑这个包含用户信息的对象。

js
const user = {
  id: 42,
  displayName: "jdoe",
  fullName: {
    firstName: "Jane",
    lastName: "Doe",
  },
};

这里我们展示了如何将传递对象的属性解构为同名变量。参数值{ id }表示将传递给函数的对象的id属性解构为同名变量,然后可以在函数中使用。

js
function userId({ id }) {
  return id;
}

console.log(userId(user)); // 42

您可以定义解构变量的名称。这里我们解构名为displayName的属性,并将其重命名为dname以在函数体中使用。

js
function userDisplayName({ displayName: dname }) {
  return dname;
}

console.log(userDisplayName(user)); // "jdoe"

嵌套对象也可以被解构。下面的示例展示了将fullname.firstName属性解构为名为name的变量。

js
function whois({ displayName, fullName: { firstName: name } }) {
  return `${displayName} is ${name}`;
}

console.log(whois(user)); // "jdoe is Jane"

设置函数参数的默认值

可以使用=指定默认值,如果传递的对象中不存在指定的属性,则将使用这些值作为变量值。

下面我们展示一个函数,其中默认大小为'big',默认坐标为x: 0, y: 0,默认半径为25。

js
function drawChart({
  size = "big",
  coords = { x: 0, y: 0 },
  radius = 25,
} = {}) {
  console.log(size, coords, radius);
  // do some chart drawing
}

drawChart({
  coords: { x: 18, y: 30 },
  radius: 30,
});

在上面drawChart的函数签名中,解构的左侧有一个默认值,为空对象= {}

您也可以在不使用该默认值的情况下编写函数。但是,如果您省略该默认值,则函数在调用时将至少查找一个要提供的参数,而在其当前形式中,您可以在不提供任何参数的情况下调用drawChart()。否则,您至少需要提供一个空对象字面量。

更多信息,请参见默认参数 > 带默认值赋值的解构参数

嵌套对象和数组解构

js
const metadata = {
  title: "Scratchpad",
  translations: [
    {
      locale: "de",
      localizationTags: [],
      lastEdit: "2014-04-14T08:43:37",
      url: "/de/docs/Tools/Scratchpad",
      title: "JavaScript-Umgebung",
    },
  ],
  url: "/en-US/docs/Tools/Scratchpad",
};

const {
  title: englishTitle, // rename
  translations: [
    {
      title: localeTitle, // rename
    },
  ],
} = metadata;

console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"

for...of 迭代和解构

js
const people = [
  {
    name: "Mike Smith",
    family: {
      mother: "Jane Smith",
      father: "Harry Smith",
      sister: "Samantha Smith",
    },
    age: 35,
  },
  {
    name: "Tom Jones",
    family: {
      mother: "Norah Jones",
      father: "Richard Jones",
      brother: "Howard Jones",
    },
    age: 25,
  },
];

for (const {
  name: n,
  family: { father: f },
} of people) {
  console.log(`Name: ${n}, Father: ${f}`);
}

// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"

计算出的对象属性名和解构

计算出的属性名,就像在对象字面量中一样,可以与解构一起使用。

js
const key = "z";
const { [key]: foo } = { z: "bar" };

console.log(foo); // "bar"

无效的 JavaScript 标识符作为属性名

通过提供一个有效的备用标识符,解构可以与不是有效 JavaScript 标识符的属性名一起使用。

js
const foo = { "fizz-buzz": true };
const { "fizz-buzz": fizzBuzz } = foo;

console.log(fizzBuzz); // true

解构原始值

对象解构几乎等同于属性访问。这意味着如果您尝试解构一个原始值,该值将被包装到相应的包装器对象中,并且将在包装器对象上访问该属性。

js
const { a, toFixed } = 1;
console.log(a, toFixed); // undefined ƒ toFixed() { [native code] }

与访问属性相同,解构nullundefined会抛出TypeError

js
const { a } = undefined; // TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined.
const { b } = null; // TypeError: Cannot destructure property 'b' of 'null' as it is null.

即使模式为空,也会发生这种情况。

js
const {} = null; // TypeError: Cannot destructure 'null' as it is null.

组合数组和对象解构

可以组合数组和对象解构。假设您想要下面数组props中的第三个元素,然后您想要对象中的name属性,您可以执行以下操作

js
const props = [
  { id: 1, name: "Fizz" },
  { id: 2, name: "Buzz" },
  { id: 3, name: "FizzBuzz" },
];

const [, , { name }] = props;

console.log(name); // "FizzBuzz"

解构对象时会查找原型链

当解构一个对象时,如果一个属性本身没有被访问,它将继续沿着原型链查找。

js
const obj = {
  self: "123",
  __proto__: {
    prot: "456",
  },
};
const { self, prot } = obj;

console.log(self); // "123"
console.log(prot); // "456"

规范

规范
ECMAScript 语言规范
# sec-destructuring-assignment
ECMAScript 语言规范
# sec-destructuring-binding-patterns

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅