34.解构

原文: http://exploringjs.com/impatient-js/ch_destructuring.html

34.1。第一次尝试解构

通过正常分配,您可以一次提取一个数据。例如,通过:

x = arr[1];

通过解构,您可以通过接收数据的位置中的模式同时提取多个数据。前一代码中=的左侧是一个这样的位置。在以下代码中,行 A 中的方括号是解构模式。它提取索引 0 和索引 1 处的 Array 元素的值:

const arr = ['a', 'b', 'c'];
const [x, y] = arr; // (A)
assert.equal(x, 'a');
assert.equal(y, 'b');

请注意,模式比数据“小”:我们只提取我们需要的东西。

34.2。构造与提取

为了理解解构是什么,请考虑 JavaScript 有两种相反的操作:

  • 您可以构建复合数据,例如通过设置属性和通过对象字面值。
  • 您可以从复合数据中提取数据,例如通过获取属性。

构建数据如下:

// Single values
const jane1 = {};
jane1.first = 'Jane';
jane1.last = 'Doe';

// Multiple values
const jane2 = {
  first: 'Jane',
  last: 'Doe',
};

assert.deepEqual(jane1, jane2);

提取数据如下:

const jane = {
  first: 'Jane',
  last: 'Doe',
};

// Single values
const f1 = jane.first;
const l1 = jane.last;
assert.equal(f1, 'Jane');
assert.equal(l1, 'Doe');

到目前为止,我们还没有看到提取多个值的方法。 _ 解构 _ 允许我们通过 _ 解构模式 _ 来做到这一点。在语法上,这样的模式看起来类似于多值构造,但是它们出现在接收数据的地方(例如,在分配的左侧),而不是在创建数据的地方(例如,在分配的右侧)。

// Multiple values
const {first: f2, last: l2} = jane; // (A)
assert.equal(f2, 'Jane');
assert.equal(l2, 'Doe');

A 行中的const声明并初始化了两个变量f2f1

34.3。我们在哪里可以破坏?

解构模式可用于“分配位置”,例如:

  • 变量声明:

    const [a] = ['x'];
    assert.equal(a, 'x');
    
    let [b] = ['y'];
    assert.equal(b, 'y');
    
  • 作业:

    let b;
    [b] = ['z'];
    assert.equal(b, 'z');
    
  • 参数定义:

    const f = ([x]) => x;
    assert.equal(f(['a']), 'a');
    

请注意,变量声明包括for-of循环中的constlet声明:

const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
    console.log(index, element);
}
// Output:
// 0, 'a'
// 1, 'b'

接下来,我们将深入研究两种解构:对象解构和数组解构。

34.4。对象解构

_ 对象解构 _ 允许您通过看起来像对象字面值的模式批量提取属性值:

const address = {
  street: 'Evergreen Terrace',
  number: '742',
  city: 'Springfield',
  state: 'NT',
  zip: '49007',
};

const { street: s, city: c } = address;
assert.equal(s, 'Evergreen Terrace');
assert.equal(c, 'Springfield');

您可以将模式视为放置在数据上的透明工作表:模式键'street'在数据中具有匹配项。因此,数据值'Evergreen Terrace'被分配给模式变量s

您还可以对象解构原始值:

const {length: len} = 'abc';
assert.equal(len, 3);

你可以对象构造数组(请记住,数组索引也是属性):

const {0:x, 2:y} = ['a', 'b', 'c'];
assert.equal(x, 'a');
assert.equal(y, 'c');

34.4.1。财产值缩写

对象字面值支持属性值缩写,对象模式也是如此:

const { street, city } = address;
assert.equal(street, 'Evergreen Terrace');
assert.equal(city, 'Springfield');

34.4.2。休息属性

在对象字面值中,您可以具有传播属性。在对象模式中,您可以拥有 rest 属性(必须具有最后的属性):

const obj = { a: 1, b: 2, c: 3 };
const { a: propValue, ...remaining } = obj; // (A)

assert.equal(propValue, 1);
assert.deepEqual(remaining, {b:2, c:3});

rest 属性变量(例如remaining(行 A))被赋予一个对象,该对象具有其模式中未提及其键的所有数据属性。

34.4.3。语法陷阱:通过对象解构分配

如果我们在赋值中进行对象解构,我们就会遇到由语法歧义引起的陷阱 - 你不能用大括号开始一个语句,因为那时 JavaScript 认为你正在启动一个块:

let value;
assert.throws(
  () => eval("{prop: value} = { prop: 'hello' };"),
  {
    name: 'SyntaxError',
    message: 'Unexpected token =',
  });

为什么eval()

我们需要通过 eval()延迟解析,否则我们在解析此代码时会遇到异常。 assert.throws()仅在其函数体内抛出异常时才有效。

解决方法是将整个作业放在括号中:

let value;
({prop: value} = { prop: 'hello' });
assert.equal(value, 'hello');

练习:对象解构

exercises/destructuring/object_destructuring_exrc.js

34.5。数组的解构

Array-destructuring 允许您通过看起来像 Array literals 的模式批量提取 Array 元素的值:

const [x, y] = ['a', 'b'];
assert.equal(x, 'a');
assert.equal(y, 'b');

您可以通过提及 Array 模式中的孔来跳过元素:

const [, x, y] = ['a', 'b', 'c']; // (A)
assert.equal(x, 'b');
assert.equal(y, 'c');

A 行中 Array 模式的第一个元素是一个洞,这就是为什么忽略索引 0 处的 Array 元素。

当操作返回数组时,数组解构很有用。例如,正则表达式方法.exec()

// Skip the element at index 0 (the whole match):
const [, year, month, day] =
  /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/
  .exec('2999-12-31');

assert.equal(year, '2999');
assert.equal(month, '12');
assert.equal(day, '31');

您还可以使用解构来交换两个变量的值,而无需临时变量:

let x = 'a';
let y = 'b';

[x,y] = [y,x]; // swap

assert.equal(x, 'b');
assert.equal(y, 'a');

34.5.1。休息元素

在 Array 字面值中,您可以使用 spread 元素。在 Array 模式中,您可以拥有 rest 元素(必须最后):

const [x, y, ...remaining] = ['a', 'b', 'c', 'd']; // (A)

assert.equal(x, 'a');
assert.equal(y, 'b');
assert.deepEqual(remaining, ['c', 'd']);

诸如remaining(行 A)之类的 rest 元素变量被赋予一个 Array,其中包含未提及的所有析构值的元素。

34.5.2。数组解构适用于任何可迭代的

Array-destructuring 可以应用于任何可迭代的值,而不仅仅是 Arrays:

// Sets are iterable
const mySet = new Set().add('a').add('b').add('c');
const [first, second] = mySet;
assert.equal(first, 'a');
assert.equal(second, 'b');

// Strings are iterable
const [a, b] = 'xyz';
assert.equal(a, 'x');
assert.equal(b, 'y');

34.6。解构用例:多个返回值

如果函数返回多个值(解包为 Array 或打包为对象),则解构非常有用。

考虑一个在数组中查找元素的函数findElement():它的参数是一个函数,它接收元素的值和索引,并返回一个布尔值,指示这是否是调用者正在寻找的元素。我们现在面临一个两难困境:findElement()应该返回它找到的元素的值还是索引?一种解决方案是创建两个单独的函数,但这会导致重复的代码,因为两个函数都非常相似。

以下实现通过返回包含找到的元素的索引和值的对象来避免重复:

function findElement(arr, predicate) {
  for (let index=0; index < arr.length; index++) {
    const element = arr[index];
    if (predicate(element)) {
      // We found something:
      return { element, index };
    }
  }
  // We didn’t find anything:
  return { element: undefined, index: -1 };
}

解构有助于我们处理findElement()的结果:

const arr = [7, 8, 6];

const {element, index} = findElement(arr, x => x % 2 === 0);
assert.equal(element, 8);
assert.equal(index, 1);

当我们使用属性键时,我们提到elementindex的顺序无关紧要:

const {index, element} = findElement(arr, x => x % 2 === 0);

如果我们只对两个结果中的一个感兴趣,那么解构也会很好地为我们服务:

const arr = [7, 8, 6];

const {element} = findElement(arr, x => x % 2 === 0);
assert.equal(element, 8);

const {index} = findElement(arr, x => x % 2 === 0);
assert.equal(index, 1);

所有这些便利性相结合,使得处理多个返回值的这种方式非常通用。

34.7。没找到匹配

如果模式的某个部分不匹配会发生什么?如果你使用非批处理运算符会发生同样的事情:你得到undefined

34.7.1。对象解构和缺少属性

如果对象模式中的属性在右侧没有匹配,则得到undefined

const {prop: p} = {};
assert.equal(p, undefined);

34.7.2。数组解构和缺少元素

如果数组模式中的元素在右侧没有匹配,则得到undefined

const [x] = [];
assert.equal(x, undefined);

34.8。哪些值无法破坏?

34.8.1。你不能对象解构undefinednull

如果要解构的值是undefinednull,则对象解构仅失败。也就是说,只要通过点运算符访问属性,它就会失败。

assert.throws(
  () => { const {prop} = undefined; },
  {
    name: 'TypeError',
    message: "Cannot destructure property `prop` of 'undefined' or 'null'.",
  }
);
assert.throws(
  () => { const {prop} = null; },
  {
    name: 'TypeError',
    message: "Cannot destructure property `prop` of 'undefined' or 'null'.",
  }
);

34.8.2。您不能使用 Array-destructure 非可迭代值

数组解构要求解构的值是可迭代的。因此,您不能使用 Array-destructure undefinednull。但是,您不能使用 Array-destructure 非可迭代对象:

assert.throws(
  () => { const [x] = {}; },
  {
    name: 'TypeError',
    message: '{} is not iterable',
  }
);

测验:基本

参见测验应用程序

34.9。 (高级)

所有其余部分都是高级的。

34.10。默认值

通常,如果模式不匹配,则相应的变量设置为undefined

const {prop: p} = {};
assert.equal(p, undefined);

使用默认值,您可以指定undefined以外的值,在这种情况下应使用该值:

const {prop: p = 123} = {}; // (A)
assert.equal(p, 123);

在 A 行中,我们将p的默认值指定为123。使用该默认值,因为我们正在解构的数据没有名为prop的属性。

34.10.1。 Array-destructuring 中的默认值

这里,我们有两个默认值分配给变量xy,因为相应的元素不存在于被解构的数组中。

const [x=1, y=2] = [];

assert.equal(x, 1);
assert.equal(y, 2);

Array 模式的第一个元素的默认值是1,第二个元素的默认值是2

34.10.2。对象解构中的默认值

您还可以为 object-destructuring 指定默认值:

const {first: f='', last: l=''} = {};
assert.equal(f, '');
assert.equal(l, '');

在解构的对象中既不存在属性键first也不存在属性键last。因此,使用默认值。

使用属性值 shorthands,此代码变得更简单:

const {first='', last=''} = {};
assert.equal(first, '');
assert.equal(last, '');

34.11。参数定义类似于解构

考虑到我们在本章中学到的内容,参数定义与数组模式(其余元素,默认值等)有很多共同之处。实际上,以下两个函数声明是等效的:

function f1(pattern1, pattern2 /* etc. */) {
  // ···
}

function f2(...args) {
  const [pattern1, pattern2 /* etc. */] = args;
  // ···
}

34.12。嵌套解构

到目前为止,我们只使用变量作为解构模式中的赋值目标。但您也可以使用模式作为赋值目标,这使您可以将模式嵌套到任意深度:

const arr = [
  { first: 'Jane', last: 'Bond' },
  { first: 'Lars', last: 'Croft' },
];
const [, {first}] = arr;
assert.equal(first, 'Lars');

在 A 行的 Array 模式中,有一个嵌套的对象模式,索引为 1。

34.13。进一步阅读

测验:高级

参见测验应用程序


书籍推荐