数组与对象的 JavaScript 解构语法
通过简洁易懂的英文解释学习 JavaScript 解构语法——数组跳过与默认值、对象别名、嵌套解包、交换操作以及函数参数(ES6+)。
解构指的是将一个结构化的值拆分开,使得每个部分都可以独立使用。无需反复访问同一个容器,只需声明期望的结构,一步即可为各部分指定名称。
一个简单的类比是从旅行背包中取出个人物品。背包代表着数组、对象、字符串或任何表现为可迭代对象的结构,而个人物品则是你想要使用的各个独立值。迭代器和可迭代对象稍后会详细讲解;目前只需知道数组及许多内置类型已经具备这种解构功能即可。
有了这样的思维模型,就可以比为每个位置编写单独的索引表达式更简洁地将这些部分绑定到变量上:
const numbers = [ 1, 2, 3 ];
// En lugar de realizar esto:
let x = numbers[0];
let y = numbers[1];
let z = numbers[2];
// Simplemente podemos hacer esto:
let [ x, y, z ] = numbers;
console.log(x); // 1
console.log(y); // 2
console.log(z); // 3
在同一个赋值操作中,你可以通过在逗号之间留空来跳过那些不关心的值。这样既能保留所需的绑定关系,又无需创建临时变量:
const numbers = [ 1, 2, 3 ];
// x = 1, z = 3
let [ x, , z ] = numbers;
// y = 2, z = 3
let [ , y, z ] = numbers;
// x = 1, y = 2, z = 3, a = undefined
let [ x, y, z, a ] = numbers;
当某个位置缺失或未定义时,你还可以使用默认值来创建新的绑定关系,这对于处理可选的尾部位置非常方便:
const numbers = [ 1, 2, 3 ];
// x = 1, y = 2, z = 3, a = 4
let [ x, y, z, a = 4 ] = numbers;
嵌套的数组包也以相同方式展开——可以直接访问内部数组,而无需使用仅为剥离一层数据而存在的临时变量:
const alphanumericSequence = [ 'a', 'b', 'c', [1, 2, 3] ];
// numericSequence = [ 1, 2, 3 ]
let [ , , , numericSequence ] = alphanumericSequence;
// x = 1, y = 2, z = 3
let [ , , , [ x, y, z ] ] = alphanumericSequence;
一个实用的技巧是在不创建临时变量的情况下交换两个变量。解构赋值可以在一条语句中完成两侧的交换,从而避免人们在手动创建第三个变量时常见的“偏移一”的错误:
let a = 1;
let b = 2;
// En lugar de hacer esto:
let temp = a;
a = b;
b = temp;
// Simplemente podemos hacer esto:
[ a, b ] = [ b, a ];
console.log(a); // 2
console.log(b); // 1
到目前为止,“背包”结构一直是一种数组,其含义由位置决定。而对象解包则遵循略有不同的规则:决定绑定关系的的是属性名称,而非顺序。
对象解构
当结构为对象时,接收绑定的名称必须与从中提取的属性名称一致——除非你使用别名对它们进行重新映射。这一规则正是与数组解包的主要区别,因为数组解包只关注顺序。
以下是一个简短示例,展示了在扁平员工记录中的应用规则:
const employee = {
name: 'Julia',
age: 27,
job: {},
workSchedule: {}
};
// El nombre de las variables coincide con el nombre
// de las propiedades del objeto.
let { name, age, job, workSchedule } = employee;
每个绑定名称都与对象上的一个属性对应,因此本地标识符本身就能清晰表达其含义。
如果这些属性名称在本地作用域中不够直观,或者与现有标识符冲突,可以在读取原始键的同时为它们分配别名:
let {
name:employeeName,
age:employeeAge,
job:employeeJob,
workSchedule:employeeWorkSchedule
} = employee;
在解构时也可以应用默认值,这样当某个属性缺失或未定义时无需后续单独进行空值检查即可填补空白:
let {
name = 'john',
age = 18,
job = 'worker',
workSchedule = null
} = employee;
// Incluso utilizar un alias durante este proceso:
let {
name:employeeName = 'john',
age:employeeAge = 18,
job:employeeJob = 'worker',
workSchedule:employeeWorkSchedule = null
} = employee;
嵌套对象同样遵循这一原则:在模式中指定路径,仅绑定所需的叶子节点:
const employee = {
name: 'Julia',
age: 27,
job: {},
workSchedule: {
mon: {
open: 12,
close: 22,
},
tue: {
open: 11,
close: 23,
},
wed: {
open: 23,
close: 5,
}
}
};
let { workSchedule: { mon, tue, wed } } = employee;
console.log(mon); // { open: 12, close: 22 }
console.log(tue); // { open: 11, close: 23 }
console.log(wed); // { open: 23, close: 5 }
对于深度嵌套的情况应保持谨慎。若在一个模式中从多层结构获取值,不仅会降低可读性,而且当结构发生变化时还会使代码难以修改:
const employee = {
name: 'Julia',
age: 27,
workSchedule: {
mon: {
open: {
from: 12
},
close: {
to: 22
}
}
}
};
// Multiples niveles de anidación
let { workSchedule: { mon: { open: {from}, close: {to} } } } = employee;
console.log(from); // 12
console.log(to); // 22
对象解构在函数参数中同样很有用。无需使用冗长的位置参数列表,只需接收一个选项对象,然后在边界处解构各字段并设置默认值即可:
const dispatchDelivery = (endpoint, { userId, time = '00:00', address, items = [] }) => {
// Some action...
};
dispatchDelivery('api/dispatch-delivery', {
userId: 3392,
address: 'Villas del Sol',
time: '22:30',
items: [ 'Beers', 'Sodas', 'Doritos' ]
});
dispatchDelivery('api/dispatch-delivery', {
userId: 5423,
address: 'Villas del Sol'
});
这样就能得到更丰富、更易阅读的签名——尤其是当某个函数需要许多调用者并不总会提供的参数时。解构是现代 JavaScript 的核心工具,与展开运算符结合使用时,其表达能力更强,还能实现用于复制和合并结构的更多组合模式。
需要注意的是语言的基准版本:解构功能是在 ECMAScript 2015(ES6)中引入的。在面向生产环境的浏览器或旧版运行时中使用它之前,较旧的引擎需要先进行转译处理或采用补丁策略。