Skip to content

解构赋值

解构赋值语法是一种 Javascript 表达式。可以将数组中的值或对象的属性取出,赋值给其他变量。—— MDN

我的理解是:解构赋值是为了便捷处理 数组、对象、函数参数的拓展。(不去专门研究对原始数据类型的解构,不钻这个牛角尖)。

ES6 允许按照一定模式,从目标数据中提取值,对变量进行赋值,这被称为解构(Destructuring)。

解构赋值写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值

如“匹配不到”变量,值就等于undefined。

绑定与赋值

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

绑定模式

以声明关键字(var、let 或 const)开始,然后,每个单独的属性必须绑定到一个变量或进一步解构。

javascript
const obj = { a: 1, b: { c: 2 } };
const { a, b: { c: d }} = obj; // 这里绑定了两个变量:`a`和`d`
const obj = { a: 1, b: { c: 2 } };
const { a, b: { c: d }} = obj; // 这里绑定了两个变量:`a`和`d`

因为所有变量共享相同的声明,可以解构两次获得可修改的和只读的数据:使用 let(可修改),使用 const(只读)。

javascript
const obj = { a: 1, b: { c: 2 } };
const { a } = obj; // a 只读 不可重新赋值
let { b: { c: d }} = obj; // d 可重新赋值
const obj = { a: 1, b: { c: 2 } };
const { a } = obj; // a 只读 不可重新赋值
let { b: { c: d }} = obj; // d 可重新赋值

赋值模式

不以关键字开头。每个解构属性都被赋值给一个赋值目标,这个赋值目标可以事先用 var 或 let 声明,也可以是另一个对象的属性,一般来说,可以是任何可以出现在赋值表达式左侧的东西。

javascript
const numbers = [];
const obj = { a: 1, b: 2 };
({ a: numbers[0], b: numbers[1] } = obj); // numbers:[1, 2]  赋值目标 事先申明
({a: x, b: y} = obj); // x: 1, y: 2  不用以关键字开头
const numbers = [];
const obj = { a: 1, b: 2 };
({ a: numbers[0], b: numbers[1] } = obj); // numbers:[1, 2]  赋值目标 事先申明
({a: x, b: y} = obj); // x: 1, y: 2  不用以关键字开头

当不使用关键开头时,在赋值语句周围必须添加括号 ( ),且表达式( )前面需要有一个分号;。否则它可能用于执行前一行的函数。

javascript
{ a, b } = { a: 1, b: 2 };  // 不是有效的独立语法,因为左侧的 {a, b} 被视为块而不是对象字面量。
({ a, b } = { a: 1, b: 2 });  // 是有效的
const { a, b } = { a: 1, b: 2 };  // 也是有效的。
{ a, b } = { a: 1, b: 2 };  // 不是有效的独立语法,因为左侧的 {a, b} 被视为块而不是对象字面量。
({ a, b } = { a: 1, b: 2 });  // 是有效的
const { a, b } = { a: 1, b: 2 };  // 也是有效的。

指定默认值

解构赋值还允许我们为变量指定默认值,当匹配不到值时,变量会被赋予默认值。

javascript
let [a,b,c,d] = [1,2,3]; // 未指定默认值
// a: 1,b: 2,c: 3, d: undefined

let [a,b,c,d = 4] = [1,2,3]; // 指定默认值
// a: 1,b: 2,c: 3, d:4
let [a,b,c,d] = [1,2,3]; // 未指定默认值
// a: 1,b: 2,c: 3, d: undefined

let [a,b,c,d = 4] = [1,2,3]; // 指定默认值
// a: 1,b: 2,c: 3, d:4

拓展运算符

javascript
const { a, ...others } = { a: 1, b: 2, c: 3 };
// others:{ b: 2, c: 3 }
const { a, ...others } = { a: 1, b: 2, c: 3 };
// others:{ b: 2, c: 3 }

适用于其他语法中

  • for...in 和 for...of 循环中的循环变量;
  • 函数参数;
  • catch 绑定变量。

解构各类数据的示例

解构数组

基本使用

javascript
const foo = ["one", "two", "three"];
const [red, yellow, green] = foo; // red:one yellow:two green:three
const foo = ["one", "two", "three"];
const [red, yellow, green] = foo; // red:one yellow:two green:three

解构比目标多

javascript
const foo = ["one", "two", "three"];
const [red, yellow, green, gray] = foo;
 // red:one;  yellow:two;  green:three;  gray:undefined
const foo = ["one", "two", "three"];
const [red, yellow, green, gray] = foo;
 // red:one;  yellow:two;  green:three;  gray:undefined

变量交换

javascript
let a = 1;
let b = 3;
[a, b] = [b, a];
// a: 3;  b:1;
let a = 1;
let b = 3;
[a, b] = [b, a];
// a: 3;  b:1;

函数返回值为数组的结构

javascript
function f() {
  return [1, 2];
}
const [a, b] = f(); // a: 1;  b:2;
function f() {
  return [1, 2];
}
const [a, b] = f(); // a: 1;  b:2;

忽略某些返回值

javascript
const [a, , b] = [1, 2, 3] // a: 1;  b:3;
const [a, , b] = [1, 2, 3] // a: 1;  b:3;

使用绑定模式作为剩余属性 去原型上面解构对象的属性、方法

javascript
const [a, b, ...{ pop, push }] = [1, 2];
console.log(a, b); // 1 2
console.log(pop, push); // [Function pop] [Function push]
const [a, b, ...{ pop, push }] = [1, 2];
console.log(a, b); // 1 2
console.log(pop, push); // [Function pop] [Function push]

从正则表达式匹配项中提取值

javascript
function parseProtocol(url) {
  const parsedURL = /^(\w+):\/\/([^/]+)\/(.*)$/.exec(url);
  if (!parsedURL) {
    return false;
  }
  console.log(parsedURL);
  // ["https://developer.mozilla.org/zh-CN/docs/Web/JavaScript",
  // "https", "developer.mozilla.org", "zh-CN/docs/Web/JavaScript"]
  const [, protocol, fullhost, fullpath] = parsedURL;
  return protocol;
}
parseProtocol("https://developer.mozilla.org/zh-CN/docs/Web/JavaScript") // // "https"
function parseProtocol(url) {
  const parsedURL = /^(\w+):\/\/([^/]+)\/(.*)$/.exec(url);
  if (!parsedURL) {
    return false;
  }
  console.log(parsedURL);
  // ["https://developer.mozilla.org/zh-CN/docs/Web/JavaScript",
  // "https", "developer.mozilla.org", "zh-CN/docs/Web/JavaScript"]
  const [, protocol, fullhost, fullpath] = parsedURL;
  return protocol;
}
parseProtocol("https://developer.mozilla.org/zh-CN/docs/Web/JavaScript") // // "https"

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

javascript
const [a, b] = new Map([[1, 2], [3, 4]]);
// a: [1, 2]
// b: [3, 4]
const [a, b] = new Map([[1, 2], [3, 4]]);
// a: [1, 2]
// b: [3, 4]

对象的解构

对象解构几乎等同于属性访问。

基本赋值

javascript
const user = {
  id: 42,
  isVerified: true,
};
const { id, isVerified } = user;
console.log(id); // 42
console.log(isVerified); // true
const user = {
  id: 42,
  isVerified: true,
};
const { id, isVerified } = user;
console.log(id); // 42
console.log(isVerified); // true

赋值给新的变量名 先匹配属性名,再赋值给变量。

javascript
const o = { p: 42, q: true };
const { p: foo, q: bar } = o;
console.log(foo); // 42
console.log(bar); // true
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 的局部变量。

从作为函数参数传递的对象中提取属性

javascript
// 从对象中解构参数
const user = {
  id: 42,
  displayName: "jdoe",
  fullName: {
    firstName: "Jane",
    lastName: "Doe",
  },
};
function userId({ id }) {
  return id;
}
userId(user) // 42

// 从对象中解构参数,重新命名参数
function userDisplayName({ displayName: dname }) {
  return dname; // 重新命名的参数 dname
}
console.log(userDisplayName(user)); // `jdoe`
// 从对象中解构参数
const user = {
  id: 42,
  displayName: "jdoe",
  fullName: {
    firstName: "Jane",
    lastName: "Doe",
  },
};
function userId({ id }) {
  return id;
}
userId(user) // 42

// 从对象中解构参数,重新命名参数
function userDisplayName({ displayName: dname }) {
  return dname; // 重新命名的参数 dname
}
console.log(userDisplayName(user)); // `jdoe`

设置函数参数的默认值

使用解构的方式,设置默认参数。

javascript
// 设置函数参数的默认值以对象举例子,也是是数组。
function drawChart({ size = "big", coords = { x: 0, y: 0 }, radius = 25} = {}) {
  console.log(size, coords, radius);
  return [size,coords,radius]
}
drawChart({ coords: { x: 18, y: 30 },radius: 30}); // ['big', { x: 18, y: 30}, 30]
drawChart(); // ['big', { x: 0, y: 0 }, 25]
// 设置函数参数的默认值以对象举例子,也是是数组。
function drawChart({ size = "big", coords = { x: 0, y: 0 }, radius = 25} = {}) {
  console.log(size, coords, radius);
  return [size,coords,radius]
}
drawChart({ coords: { x: 18, y: 30 },radius: 30}); // ['big', { x: 18, y: 30}, 30]
drawChart(); // ['big', { x: 0, y: 0 }, 25]

通常简洁的写法,不使用解构的方式。

javascript
function multiply(a, b = 1) {
  return a * b;
}
console.log(multiply(5, 2));// 10
console.log(multiply(5));// 5
function multiply(a, b = 1) {
  return a * b;
}
console.log(multiply(5, 2));// 10
console.log(multiply(5));// 5

解构嵌套对象和数组

javascript
const metadata = {
  title: "Scratchpad",
  translations: [
    {
      locale: "de",
      localization_tags: [],
      last_edit: "2014-04-14T08:43:37",
      url: "/de/docs/Tools/Scratchpad",
      title: "JavaScript-Umgebung",
    },
  ],
  url: "/zh-CN/docs/Tools/Scratchpad",
};

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

console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"
const metadata = {
  title: "Scratchpad",
  translations: [
    {
      locale: "de",
      localization_tags: [],
      last_edit: "2014-04-14T08:43:37",
      url: "/de/docs/Tools/Scratchpad",
      title: "JavaScript-Umgebung",
    },
  ],
  url: "/zh-CN/docs/Tools/Scratchpad",
};

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

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

For of 迭代和解构

javascript
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"
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"

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

javascript
const obj = {
  self: "123",
  __proto__: {
    prot: "456",
  },
};
const { self, prot } = obj;
// self "123"
// prot "456" (Access to the prototype chain)
const obj = {
  self: "123",
  __proto__: {
    prot: "456",
  },
};
const { self, prot } = obj;
// self "123"
// prot "456" (Access to the prototype chain)

解构原始数据类型

如果尝试解构基本类型的值,该值将被包装到相应的包装器对象中,并且在包装器对象上访问该属性。

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

解构 null 或 undefined 会抛出 TypeError