解構賦值

解構賦值語法是 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.club.tw/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.club.tw/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: "/en-US/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 表格僅在瀏覽器中載入

另請參閱