使用普通的赋值,您可以一次提取一条数据——例如
const arr = ['a', 'b', 'c'];
const x = arr[0]; // extract
const y = arr[1]; // extract
使用解构,您可以通过接收数据的模式一次提取多条数据。 上述代码中 `=` 的左侧就是这样一个位置。 在下面的代码中,A 行中的方括号是一个解构模式
const arr = ['a', 'b', 'c'];
const [x, y] = arr; // (A)
.equal(x, 'a');
assert.equal(y, 'b'); assert
这段代码与前面的代码作用相同。
请注意,模式比数据“小”:我们只提取我们需要的内容。
为了理解什么是解构,请考虑 JavaScript 有两种相反的操作
构造数据如下所示
// Constructing: one property at a time
const jane1 = {};
.first = 'Jane';
jane1.last = 'Doe';
jane1
// Constructing: multiple properties
const jane2 = {
first: 'Jane',
last: 'Doe',
;
}
.deepEqual(jane1, jane2); assert
提取数据如下所示
const jane = {
first: 'Jane',
last: 'Doe',
;
}
// Extracting: one property at a time
const f1 = jane.first;
const l1 = jane.last;
.equal(f1, 'Jane');
assert.equal(l1, 'Doe');
assert
// Extracting: multiple properties (NEW!)
const {first: f2, last: l2} = jane; // (A)
.equal(f2, 'Jane');
assert.equal(l2, 'Doe'); assert
A 行中的操作是新的:我们声明了两个变量 `f2` 和 `l2`,并通过*解构*(多值提取)初始化它们。
A 行的以下部分是*解构模式*
first: f2, last: l2} {
解构模式在语法上类似于用于多值构造的字面量。 但它们出现在接收数据的地方(例如,赋值的左侧),而不是创建数据的地方(例如,赋值的右侧)。
解构模式可以在“数据接收位置”使用,例如
变量声明
const [a] = ['x'];
.equal(a, 'x');
assert
let [b] = ['y'];
.equal(b, 'y'); assert
赋值
let b;
= ['z'];
[b] .equal(b, 'z'); assert
参数定义
const f = ([x]) => x;
.equal(f(['a']), 'a'); assert
请注意,变量声明包括 `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;
.equal(s, 'Evergreen Terrace');
assert.equal(c, 'Springfield'); assert
您可以将模式想象成放置在数据上的一张透明纸:模式键 `'street'` 在数据中具有匹配项。 因此,数据值 `'Evergreen Terrace'` 被分配给模式变量 `s`。
您还可以对原始值进行对象解构
const {length: len} = 'abc';
.equal(len, 3); assert
您还可以对数组进行对象解构
const {0:x, 2:y} = ['a', 'b', 'c'];
.equal(x, 'a');
assert.equal(y, 'c'); assert
为什么这样做有效? 数组索引也是属性。
对象字面量支持属性值简写,对象模式也支持
const { street, city } = address;
.equal(street, 'Evergreen Terrace');
assert.equal(city, 'Springfield'); assert
练习:对象解构
exercises/destructuring/object_destructuring_exrc.mjs
在对象字面量中,您可以使用扩展属性。 在对象模式中,您可以使用剩余属性(必须放在最后)
const obj = { a: 1, b: 2, c: 3 };
const { a: propValue, ...remaining } = obj; // (A)
.equal(propValue, 1);
assert.deepEqual(remaining, {b:2, c:3}); assert
剩余属性变量(例如 `remaining`(A 行))被分配了一个对象,该对象包含所有数据属性,其键未在模式中提及。
`remaining` 也可以被视为从 `obj` 中非破坏性地删除属性 `a` 的结果。
如果我们在赋值中进行对象解构,我们会遇到由 语法歧义 引起的陷阱——您不能用花括号开始语句,因为那样 JavaScript 会认为您要开始一个代码块
let prop;
.throws(
assert=> eval("{prop} = { prop: 'hello' };"),
()
{name: 'SyntaxError',
message: "Unexpected token '='",
; })
为什么使用 `eval()`?
`eval()` 会延迟解析(因此也会延迟 `SyntaxError`),直到执行 `assert.throws()` 的回调。 如果我们不使用它,我们在解析此代码时就已经会收到错误,并且 `assert.throws()` 甚至不会被执行。
解决方法是将整个赋值放在括号中
let prop;
= { prop: 'hello' });
({prop} .equal(prop, 'hello'); assert
*数组解构*允许您通过类似于数组字面量的模式批量提取数组元素的值
const [x, y] = ['a', 'b'];
.equal(x, 'a');
assert.equal(y, 'b'); assert
您可以通过在数组模式中提及空位来跳过元素
const [, x, y] = ['a', 'b', 'c']; // (A)
.equal(x, 'b');
assert.equal(y, 'c'); assert
A 行中数组模式的第一个元素是一个空位,这就是为什么数组中索引 0 处的元素被忽略的原因。
数组解构可以应用于任何可迭代的值,而不仅仅是数组
// Sets are iterable
const mySet = new Set().add('a').add('b').add('c');
const [first, second] = mySet;
.equal(first, 'a');
assert.equal(second, 'b');
assert
// Strings are iterable
const [a, b] = 'xyz';
.equal(a, 'x');
assert.equal(b, 'y'); assert
在数组字面量中,您可以使用扩展元素。 在数组模式中,您可以使用剩余元素(必须放在最后)
const [x, y, ...remaining] = ['a', 'b', 'c', 'd']; // (A)
.equal(x, 'a');
assert.equal(y, 'b');
assert.deepEqual(remaining, ['c', 'd']); assert
剩余元素变量(例如 `remaining`(A 行))被分配了一个数组,该数组包含解构值中尚未提及的所有元素。
您可以使用数组解构来交换两个变量的值,而无需使用临时变量
let x = 'a';
let y = 'b';
,y] = [y,x]; // swap
[x
.equal(x, 'b');
assert.equal(y, 'a'); assert
当操作返回数组时,数组解构很有用,例如正则表达式方法 `.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');
.equal(year, '2999');
assert.equal(month, '12');
assert.equal(day, '31'); assert
如果函数返回多个值——打包为数组或打包为对象——则解构非常有用。
考虑一个在数组中查找元素的函数 `findElement()`
findElement(array, (value, index) => «boolean expression»)
它的第二个参数是一个函数,该函数接收元素的值和索引,并返回一个布尔值,指示这是否是调用者正在寻找的元素。
我们现在面临一个困境:`findElement()` 应该返回找到的元素的值还是索引? 一种解决方案是创建两个单独的函数,但这会导致代码重复,因为这两个函数非常相似。
以下实现通过返回一个包含找到的元素的索引和值的*对象*来避免重复
function findElement(arr, predicate) {
for (let index=0; index < arr.length; index++) {
const value = arr[index];
if (predicate(value)) {
// We found something:
return { value, index };
}
}// We didn’t find anything:
return { value: undefined, index: -1 };
}
解构帮助我们处理 `findElement()` 的结果
const arr = [7, 8, 6];
const {value, index} = findElement(arr, x => x % 2 === 0);
.equal(value, 8);
assert.equal(index, 1); assert
当我们使用属性键时,我们提及 `value` 和 `index` 的顺序无关紧要
const {index, value} = findElement(arr, x => x % 2 === 0);
关键是,如果我们只对两个结果中的一个感兴趣,解构也能很好地为我们服务
const arr = [7, 8, 6];
const {value} = findElement(arr, x => x % 2 === 0);
.equal(value, 8);
assert
const {index} = findElement(arr, x => x % 2 === 0);
.equal(index, 1); assert
所有这些便利加在一起,使得这种处理多个返回值的方式非常通用。
如果模式的一部分没有匹配项会发生什么? 与使用非批量运算符时发生的情况相同:您将获得 `undefined`。
如果对象模式中的属性在右侧没有匹配项,您将获得 `undefined`
const {prop: p} = {};
.equal(p, undefined); assert
如果数组模式中的元素在右侧没有匹配项,您将获得 `undefined`
const [x] = [];
.equal(x, undefined); assert
仅当要解构的值为 `undefined` 或 `null` 时,对象解构才会失败。 也就是说,只要通过点运算符访问属性会失败,它就会失败。
> const {prop} = undefinedTypeError: Cannot destructure property 'prop' of 'undefined'
as it is undefined.
> const {prop} = nullTypeError: Cannot destructure property 'prop' of 'null'
as it is null.
数组解构要求解构的值是可迭代的。 因此,您不能对 `undefined` 和 `null` 进行数组解构。 但是您也不能对不可迭代的对象进行数组解构
> const [x] = {}TypeError: {} is not iterable
测验:基础
请参阅测验应用程序。
以下所有部分均为进阶内容。
通常,如果模式没有匹配项,则相应的变量将设置为 `undefined`
const {prop: p} = {};
.equal(p, undefined); assert
如果您想使用不同的值,则需要指定*默认值*(通过 `=`)
const {prop: p = 123} = {}; // (A)
.equal(p, 123); assert
在 A 行中,我们将 `p` 的默认值指定为 `123`。 使用该默认值是因为我们正在解构的数据没有名为 `prop` 的属性。
在这里,我们有两个默认值分配给变量 `x` 和 `y`,因为相应的元素在被解构的数组中不存在。
const [x=1, y=2] = [];
.equal(x, 1);
assert.equal(y, 2); assert
数组模式的第一个元素的默认值为 `1`;第二个元素的默认值为 `2`。
您还可以为对象解构指定默认值
const {first: f='', last: l=''} = {};
.equal(f, '');
assert.equal(l, ''); assert
被解构的对象中既不存在属性键 `first` 也不存在属性键 `last`。 因此,使用默认值。
使用属性值简写,此代码变得更简单
const {first='', last=''} = {};
.equal(first, '');
assert.equal(last, ''); assert
考虑到我们在本章中学到的内容,参数定义与数组模式(剩余元素、默认值等)有很多共同点。 实际上,以下两个函数声明是等效的
function f1(«pattern1», «pattern2») {
// ···
}
function f2(...args) {
const [«pattern1», «pattern2»] = args;
// ···
}
到目前为止,我们只在解构模式中使用变量作为*赋值目标*(数据接收器)。 但是您也可以使用模式作为赋值目标,这使您能够将模式嵌套到任意深度
const arr = [
first: 'Jane', last: 'Bond' },
{ first: 'Lars', last: 'Croft' },
{ ;
]const [, {first}] = arr; // (A)
.equal(first, 'Lars'); assert
在 A 行的数组模式中,索引 1 处有一个嵌套的对象模式。
嵌套模式可能会变得难以理解,因此最好适度使用。
测验:进阶
请参阅测验应用程序。