原文: http://exploringjs.com/impatient-js/ch_destructuring.html
通过正常分配,您可以一次提取一个数据。例如,通过:
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');
请注意,模式比数据“小”:我们只提取我们需要的东西。
为了理解解构是什么,请考虑 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
声明并初始化了两个变量f2
和f1
。
解构模式可用于“分配位置”,例如:
变量声明:
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
循环中的const
和let
声明:
const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
console.log(index, element);
}
// Output:
// 0, 'a'
// 1, 'b'
接下来,我们将深入研究两种解构:对象解构和数组解构。
_ 对象解构 _ 允许您通过看起来像对象字面值的模式批量提取属性值:
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');
对象字面值支持属性值缩写,对象模式也是如此:
const { street, city } = address;
assert.equal(street, 'Evergreen Terrace');
assert.equal(city, 'Springfield');
在对象字面值中,您可以具有传播属性。在对象模式中,您可以拥有 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))被赋予一个对象,该对象具有其模式中未提及其键的所有数据属性。
如果我们在赋值中进行对象解构,我们就会遇到由语法歧义引起的陷阱 - 你不能用大括号开始一个语句,因为那时 JavaScript 认为你正在启动一个块:
let value;
assert.throws(
() => eval("{prop: value} = { prop: 'hello' };"),
{
name: 'SyntaxError',
message: 'Unexpected token =',
});
我们需要通过 eval()延迟解析,否则我们在解析此代码时会遇到异常。 assert.throws()
仅在其函数体内抛出异常时才有效。
解决方法是将整个作业放在括号中:
let value;
({prop: value} = { prop: 'hello' });
assert.equal(value, 'hello');
exercises/destructuring/object_destructuring_exrc.js
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');
在 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,其中包含未提及的所有析构值的元素。
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');
如果函数返回多个值(解包为 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);
当我们使用属性键时,我们提到element
和index
的顺序无关紧要:
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);
所有这些便利性相结合,使得处理多个返回值的这种方式非常通用。
如果模式的某个部分不匹配会发生什么?如果你使用非批处理运算符会发生同样的事情:你得到undefined
。
如果对象模式中的属性在右侧没有匹配,则得到undefined
:
const {prop: p} = {};
assert.equal(p, undefined);
如果数组模式中的元素在右侧没有匹配,则得到undefined
:
const [x] = [];
assert.equal(x, undefined);
undefined
和null
如果要解构的值是undefined
或null
,则对象解构仅失败。也就是说,只要通过点运算符访问属性,它就会失败。
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'.",
}
);
数组解构要求解构的值是可迭代的。因此,您不能使用 Array-destructure undefined
和null
。但是,您不能使用 Array-destructure 非可迭代对象:
assert.throws(
() => { const [x] = {}; },
{
name: 'TypeError',
message: '{} is not iterable',
}
);
参见测验应用程序。
所有其余部分都是高级的。
通常,如果模式不匹配,则相应的变量设置为undefined
:
const {prop: p} = {};
assert.equal(p, undefined);
使用默认值,您可以指定undefined
以外的值,在这种情况下应使用该值:
const {prop: p = 123} = {}; // (A)
assert.equal(p, 123);
在 A 行中,我们将p
的默认值指定为123
。使用该默认值,因为我们正在解构的数据没有名为prop
的属性。
这里,我们有两个默认值分配给变量x
和y
,因为相应的元素不存在于被解构的数组中。
const [x=1, y=2] = [];
assert.equal(x, 1);
assert.equal(y, 2);
Array 模式的第一个元素的默认值是1
,第二个元素的默认值是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, '');
考虑到我们在本章中学到的内容,参数定义与数组模式(其余元素,默认值等)有很多共同之处。实际上,以下两个函数声明是等效的:
function f1(pattern1, pattern2 /* etc. */) {
// ···
}
function f2(...args) {
const [pattern1, pattern2 /* etc. */] = args;
// ···
}
到目前为止,我们只使用变量作为解构模式中的赋值目标。但您也可以使用模式作为赋值目标,这使您可以将模式嵌套到任意深度:
const arr = [
{ first: 'Jane', last: 'Bond' },
{ first: 'Lars', last: 'Croft' },
];
const [, {first}] = arr;
assert.equal(first, 'Lars');
在 A 行的 Array 模式中,有一个嵌套的对象模式,索引为 1。
参见测验应用程序。