解构赋值(Destructuring Assignment)是 ES6 引入的最实用的语法特性之一。它允许你通过一种匹配模式,从数组或对象中提取值并直接赋给变量。原本需要多行代码才能完成的操作,现在一行就能搞定,不仅代码更简洁,意图也更清晰。解构赋值分为三大类:数组解构、对象解构,以及它们的延伸——函数参数解构。
11.1.1 数组解构
数组解构按照位置顺序提取值,等号左边是变量组成的“模式”,右边是要解构的数组。
1. 基础语法
const arr = [1, 2, 3];
// 传统做法
const a = arr[0];
const b = arr[1];
const c = arr[2];
// 解构赋值
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3
可以只取前几个值,后续元素被忽略:
const [first, second] = [10, 20, 30];
console.log(first); // 10
console.log(second); // 20
可以跳过某些位置,用逗号留空即可:
const [, , third] = [10, 20, 30];
console.log(third); // 30
2. 默认值
当右侧数组对应位置的值是 undefined 时,可以使用默认值:
const [x = 1, y = 2] = [5];
console.log(x); // 5 (有值)
console.log(y); // 2 (默认为2)
只有严格等于 undefined 才会触发默认值,null 不会被替换:
const [a = 1] = [null];
console.log(a); // null,不是 1
3. 交换变量值
解构赋值让交换两个变量的值不再需要临时变量:
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
4. 嵌套数组解构
数组本身可以包含数组,解构时模式可以进行嵌套:
const [first, [second, third]] = [1, [2, 3]];
console.log(first); // 1
console.log(second); // 2
console.log(third); // 3
5. 剩余元素 ... 收集
使用剩余运算符可以将剩余的元素收集到一个新数组中:
const [head, ...tail] = [1, 2, 3, 4];
console.log(head); // 1
console.log(tail); // [2, 3, 4]
剩余元素必须是解构模式中的最后一个变量,不能放在中间。
11.1.2 对象解构
对象解构按照属性名匹配,不需要关心顺序,比数组解构更灵活。
1. 基础语法
const user = { name: "张三", age: 25, city: "北京" };
// 传统做法
const name = user.name;
const age = user.age;
// 对象解构
const { name, age } = user;
console.log(name, age); // 张三 25
变量名必须与对象属性名相同,如果想用不同的变量名,需要使用别名:
const { name: userName, age: userAge } = user;
console.log(userName); // 张三
console.log(userAge); // 25
别名语法中,冒号左边是匹配的属性名,右边是新变量名。可以简记为“属性: 别名”。
2. 默认值
类似数组解构,属性值为 undefined 时可指定默认值:
const { name, gender = "男" } = { name: "李四" };
console.log(name); // 李四
console.log(gender); // 男
默认值也可以和别名结合:
const { age: userAge = 18 } = {};
console.log(userAge); // 18
3. 嵌套对象解构
对象内部可以嵌套对象,解构时可以使用多层模式:
const response = {
data: {
user: {
id: 1,
profile: { nickname: "小明" }
}
}
};
const { data: { user: { profile: { nickname } } } } = response;
console.log(nickname); // 小明
这样深层解构虽然方便,但要注意:如果链上任何一个属性不存在,都会报错。对于可能缺失的属性,可以搭配默认值或使用可选链:
const { data: { user: { profile: { nickname = "匿名" } = {} } = {} } = {} } = response;
不过这种防御式写法较为繁琐,实际项目中更推荐使用可选链 ?. 提前判断。
4. 剩余属性收集
剩余运算符也可以用于对象解构,将未解构的属性打包到一个新对象中:
const { name, ...rest } = { name: "王五", age: 30, city: "上海" };
console.log(name); // 王五
console.log(rest); // { age: 30, city: "上海" }
这在需要排除某些属性、传递剩余 props 的场景(如 React 组件)中非常实用。
5. 解构已有变量
如果将对象解构赋值给已经声明的变量,必须用圆括号包裹整个赋值语句,否则 JavaScript 引擎会将花括号理解为代码块:
let name, age;
({ name, age } = { name: "赵六", age: 40 }); // 必须加括号
console.log(name, age); // 赵六 40
11.1.3 参数解构
函数参数的解构实际上是数组解构和对象解构在函数定义中的直接应用。它让函数签名更清晰地表达所需参数的结构,同时省去了在函数内部手动解构的步骤。
1. 对象参数解构
这是最常用的形式,尤其当一个函数需要多个参数时,传入一个对象并在参数位置解构,可以避免参数顺序的困扰:
function createUser({ name, age, isAdmin = false }) {
console.log(name, age, isAdmin);
}
createUser({ name: "孙七", age: 28 }); // 孙七 28 false
传入的对象可以包含额外属性,解构时会自动忽略未声明的属性。默认值的应用让参数具有清晰的可选行为。
2. 数组参数解构
当函数接收一个数组作为参数时,也可以用解构语法:
function sum([a, b]) {
return a + b;
}
console.log(sum([3, 5])); // 8
3. 参数默认值与解构默认值组合
可以为整个解构参数设置一个默认的空对象,以防止不传参导致解构 undefined 时报错:
function print({ name = "匿名", age = 0 } = {}) {
console.log(name, age);
}
print(); // 匿名 0 (参数默认值生效)
print({ name: "周八" }); // 周八 0 (name被传入,age使用默认值)
注意 = {} 是给整个参数对象的默认值,而 name = "匿名" 是给解构属性的默认值,两者作用层面不同。
4. 剩余参数与解构结合
函数参数中同样可以使用剩余运算符收集其余参数:
function test(first, ...rest) {
console.log(first, rest);
}
test(1, 2, 3, 4); // 1 [2, 3, 4]
也可以在对象解构中将未解构的属性收集到 rest 对象中,常用于过滤特定属性:
function handleRequest({ url, ...options }) {
console.log(url);
console.log(options); // 其他配置项
}
11.1.4 解构的实际应用场景
- 处理函数返回值
一个函数返回数组或对象时,解构可以一次性取出多个值,非常清晰:
function getPosition() {
return { x: 10, y: 20 };
}
const { x, y } = getPosition();
- 导入模块的具名导出
ES Modules 中,import { useState, useEffect } from 'react' 本身就是对象解构的语法形式。
- 遍历 Map 结构
在 for...of 循环里用解构取出键值对:
const map = new Map();
map.set('a', 1);
for (const [key, value] of map) {
console.log(key, value);
}
- 提取 JSON 数据
在请求接口拿到嵌套 JSON 后,解构能快速提取深层字段,避免多层点号访问。
11.1.5 常见错误与注意事项
- 不要混淆对象解构的冒号:
{ name: userName }的意思是“取出 name 属性并赋值给变量 userName”,不是定义一个对象。 undefined或null解构报错:不能对undefined或null进行对象/数组解构,会导致运行时错误。必要时在解构前给予兜底或选用可选链。- 解构不会改变原对象/数组,只是将值复制到新变量(对于基本类型是值复制,对于引用类型是浅复制)。
- 解构赋值仅在支持 ES6 的环境中可用,但现在所有现代浏览器和 Node.js 版本都已原生支持,无需编译即可使用。
解构赋值是从 ES6 开始就应该默认开启的编码习惯,它让代码更具可读性,减少了大量繁琐的中间变量声明。下一节我们将继续介绍另一个同样高频使用的特性:扩展运算符与剩余运算符。