解构就是按结构做模式匹配
解构让你写出一个与对象或数组结构相对应的模式,然后在一行代码里按名称或位置把值取出来。你不必一个属性一个属性地去取值,只需描述你想要什么,JavaScript 就会交给你。
两种形式:
花括号按属性名匹配对象,方括号按位置匹配数组。在这两种情况下,左边的变量都是新声明的变量:你不是在对值做索引,而是在描述一个提取各部分的模式。
在解构出现之前,同样的代码是这样写的:
const name = user.name;
const age = user.age;
const first = scores[0];
const second = scores[1];
倒也不是什么大问题,但要是每个函数都这么写,就显得很啰嗦了。
对象解构使用属性名
对于对象,花括号里的名称必须与源对象的属性名一致。顺序无关紧要,JavaScript 会按键去查找。
如果对象上不存在某个属性,对应的变量就是 undefined,不会报错:
一定要记住这种悄无声息返回 undefined 的行为,不然哪天属性名打错了一个字母,你会在凌晨两点百思不得其解。
重命名变量
有时对象上的属性名并不是你想要的变量名:也许它与作用域中的其他名称冲突,也许 API 的命名风格你不喜欢。用 original: newName 重新绑定:
把它读作“取出 id 属性,命名为 userId”。属性名在前,冒号后面是新的变量名。它看起来像其他语言里的类型注解,但其实不是。
默认值
如果某个属性可能缺失,可以直接在模式中给它一个默认值:
默认值只在值为 undefined 时生效。null、0 和 "" 都会原样保留:
很多人在这里栽过跟头。如果你希望 null 也触发默认值,就需要显式检查,或者在解构之后使用空值合并运算符 ??。
默认值还可以和重命名叠加使用:
数组解构使用位置
数组没有名称,只有索引,所以模式按位置匹配。用逗号跳过你不关心的元素:
开头的两个逗号表示“跳过索引 0 和 1”。看起来有点密集,但当你只想取中间的某个值时很方便。
数组解构在返回元组的函数中特别好用:
同样的模式在 React hooks 中随处可见:const [count, setCount] = useState(0)。
剩余模式:收集剩下的部分
在解构模式中,...name 会把所有没有按名称或位置匹配到的内容收集到一个新变量里。
对于数组,剩余模式取走尾部:
对于对象,剩余模式取走所有没有被显式列出的属性:
这是在不修改原对象的前提下去掉某个字段的惯用写法:rest 是一个新对象,包含除 id 之外的所有内容。
剩余模式必须是模式中的最后一个元素。const [...init, last] 是语法错误。
嵌套解构
模式可以嵌套。如果某个属性本身是对象或数组,你可以在同一个表达式中继续解构它:
功能很强大,但要谨慎使用。三层嵌套的花括号读起来就开始像解谜了。到了这一步,就把中间值提取到单独的变量里:可读性比耍聪明更重要。
有一个坑:模式中的 data: { user } 并不会创建名为 data 的变量。它只是一条指令,意思是“进入 data 继续解构”。如果你需要 data 本身,就单独再写一次:
解构函数参数
你最常使用解构的地方是函数的参数列表。它能把“传入一个选项对象”变成一个自带说明的函数签名:
在调用处,它看起来和其他传选项对象的调用没什么两样。在函数内部,你得到的是带默认值的具名变量,再也不用写 options.name || "default" 这类样板代码了。
有一个容易踩的坑:不传任何参数调用这个函数会直接崩溃,因为 undefined 无法被解构。给整个参数设置一个默认值:
模式后面的 = {} 表示“如果没有传入参数,就当它是一个空对象”。然后内层的默认值就会接手。
交换与重新赋值
解构同样适用于已经声明过的变量,但对象模式两边需要加上括号(否则 JavaScript 会把 { 当作代码块的开始):
数组交换写起来很干净。对象的重新赋值比较少见,但这个加括号的技巧值得记下来,需要时就能用上。
一个贴近实战的例子
把上面讲的大部分内容组合起来:一个处理 API 响应的函数:
每一层解构都有默认值,所以面对不完整的数据,函数也能正常运行,不需要一堆 if 判断。这才是真正的好处:函数签名既精确又宽容。
下一步:对象展开
解构把值从对象中取出来,展开则把值放进去:复制、合并和覆盖。在现代 JavaScript 中,两者形影不离。下一篇就讲展开。
常见问题
JavaScript 中的解构赋值是什么?
解构是一种语法,可以一步从对象或数组中取出值并绑定到变量上。const { name } = user; 从 user 中取出 name 属性。const [first, second] = list; 取出 list 的前两个元素。它是写在 = 左边的一个模式,与右边的值的结构相对应。
解构时如何重命名变量?
使用 { original: newName } 语法。const { name: username } = user; 从 user 中取出 name 属性,并把它绑定到名为 username 的局部变量上。它很容易被误读成类型注解。记住:属性名在前,冒号后面才是新的变量名。
解构时可以设置默认值吗?
可以。const { timeout = 5000 } = options; 在 options.timeout 为 undefined 时使用 5000。默认值只对 undefined 生效:null、0 和 '' 都会原样保留。默认值还可以和重命名结合使用:const { t: timeout = 5000 } = options;。
解构和展开运算符有什么区别?
它们看起来很像,作用却正好相反。解构(const { a, b } = obj)是把值从结构中取出来。展开(const copy = { ...obj })是把值放进一个新的结构中。解构模式中的 ... 是剩余模式,它会把剩下的部分收集到一个新变量里。