JS 中,解构是一种便捷的语法,用于从数组或对象中提取值,并将其赋值给变量。它可以简化代码,让数据提取更直观。解构主要分为 数组解构 和 对象解构。
一、数组解构
数组解构是一种通过索引位置匹配数组元素,并将其赋值给对应变量的语法。由于数组是有序集合,解构时变量的位置与数组元素的位置严格对应,语法上使用方括号 [] 包裹变量。
1. 基本用法
数组解构的核心逻辑是“按位置提取”:左侧方括号中的变量按顺序对应右侧数组中的元素,第一个变量匹配数组第一个元素,第二个变量匹配数组第二个元素,以此类推。
// 左侧变量 [a, b, c] 按顺序匹配右侧数组 [1, 2, 3] 的元素
const [a, b, c] = [1, 2, 3];
console.log(a); // 1(变量a匹配数组索引0的元素)
console.log(b); // 2(变量b匹配数组索引1的元素)
console.log(c); // 3(变量c匹配数组索引2的元素)
如果数组长度大于变量数量,多余的元素会被忽略;如果变量数量多于数组长度,多余的变量会被赋值为 undefined:
const [x, y] = [10];
console.log(x); // 10(数组有第一个元素)
console.log(y); // undefined(数组没有第二个元素,变量y无匹配值)
2. 跳过元素
当需要提取数组中间或后面的元素,而忽略前面的部分元素时,可以通过空逗号来“跳过”不需要的位置。逗号的数量代表跳过的元素个数,空逗号之间的位置会被忽略。
// 数组 [10, 20, 30] 中,第一个逗号跳过索引1的元素(20)
const [x, , y] = [10, 20, 30];
console.log(x); // 10(匹配索引0的元素)
console.log(y); // 30(跳过索引1,匹配索引2的元素)
例如,提取数组的第一个和第四个元素,需跳过中间两个元素:
const [first, , , fourth] = [1, 2, 3, 4];
console.log(first); // 1
console.log(fourth); // 4
3. 剩余元素(Rest)
当需要提取数组的前几个元素,同时将剩余所有元素统一收集到一个新数组中时,可以使用 ...变量名 的语法(称为“剩余操作符”)。剩余变量必须放在所有变量的最后一位,否则会报错。
// 提取第一个元素后,将剩余元素收集到 rest 数组中
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1(提取第一个元素)
console.log(rest); // [2, 3, 4](剩余元素组成的新数组)
注意:剩余操作符只能收集“剩余”的元素,不能单独使用或放在中间:
// 错误示例:剩余变量不能放在中间
const [a, ...rest, b] = [1, 2, 3, 4]; // 语法报错
4. 默认值
如果数组中某个位置的元素不存在(或为 undefined),解构时可以为对应的变量设置默认值。默认值仅在“无匹配元素”或“匹配元素为 undefined”时生效,若元素为 null、0 等其他值,会使用元素本身。
// 数组 [5] 只有一个元素,变量b无匹配值,使用默认值0
const [a = 0, b = 0] = [5];
console.log(a); // 5(数组有元素,使用原值)
console.log(b); // 0(无匹配元素,使用默认值)
// 若元素为undefined,默认值同样生效
const [c = 10] = [undefined];
console.log(c); // 10(匹配元素为undefined,使用默认值)
5. 交换变量
传统交换两个变量的值需要借助临时变量,而数组解构可以直接通过“位置互换”实现变量交换,原理是将右侧数组的元素按新顺序赋值给左侧变量。
let x = 1, y = 2;
// 传统交换:需要临时变量temp
let temp = x;
x = y;
y = temp;
// 解构交换:右侧数组[y, x]的元素按顺序赋值给左侧[x, y]
[x, y] = [y, x];
console.log(x, y); // 2, 1(变量值成功交换)
这种方式不仅简洁,还能避免临时变量的创建,适用于排序、算法等需要交换值的场景。
二、对象解构
对象解构是通过属性名匹配对象属性,并将属性值赋值给对应变量的语法。由于对象是键值对集合,解构时变量名需与对象的属性名一致(或通过重命名指定对应关系),语法上使用花括号 {} 包裹变量。
1. 基本用法
对象解构的核心逻辑是“按属性名提取”:左侧花括号中的变量名必须与右侧对象的属性名一致,变量会被赋值为对应属性的值,与变量的顺序无关。
const user = { name: 'Alice', age: 30 };
// 变量name匹配对象的name属性,变量age匹配对象的age属性
const { name, age } = user;
console.log(name); // 'Alice'(属性名匹配成功)
console.log(age); // 30(属性名匹配成功)
如果变量名在对象中不存在,变量会被赋值为 undefined:
const { gender } = user; // user对象中没有gender属性
console.log(gender); // undefined
2. 重命名变量
当希望使用与对象属性名不同的变量名时,可以通过 新变量名: 原属性名 的语法重命名。此时,新变量名用于接收值,原属性名用于匹配对象属性。
const user = { name: 'Bob', age: 25 };
// 用userName接收name属性的值,用userAge接收age属性的值
const { name: userName, age: userAge } = user;
console.log(userName); // 'Bob'(新变量名生效)
console.log(userAge); // 25(新变量名生效)
// 原变量名name和age未被定义,使用会报错
console.log(name); // ReferenceError: name is not defined
重命名常用于避免变量名冲突(例如,当对象属性名与当前作用域中的变量名重复时)。
3. 默认值
与数组解构类似,对象解构也可以为变量设置默认值。当对象中不存在对应的属性,或属性值为 undefined 时,默认值生效;若属性值为 null、0 等其他值,会使用属性本身的值。
// 对象中只有age属性,city属性不存在,使用默认值'Beijing'
const { city = 'Beijing', age = 18 } = { age: 20 };
console.log(city); // 'Beijing'(属性不存在,用默认值)
console.log(age); // 20(属性存在,用原值)
// 若属性值为undefined,默认值生效
const { score = 60 } = { score: undefined };
console.log(score); // 60(属性值为undefined,用默认值)
默认值也可以结合重命名使用:
// 重命名属性,并设置默认值
const { name: userName = 'Guest' } = {}; // 对象中无name属性
console.log(userName); // 'Guest'(默认值生效)
4. 剩余属性(Rest)
当需要提取对象的部分属性,同时将剩余所有属性统一收集到一个新对象中时,可以使用 ...变量名 的语法。剩余变量必须放在所有变量的最后一位,否则会报错。
// 提取a和b属性后,将剩余属性收集到rest对象中
const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a); // 1(提取a属性)
console.log(b); // 2(提取b属性)
console.log(rest); // { c: 3, d: 4 }(剩余属性组成的新对象)
剩余属性常用于筛选对象中的部分属性,或复制对象时排除特定属性:
const user = { id: 1, name: 'Alice', password: '123' };
// 提取password,剩余属性作为安全信息(不含密码)
const { password, ...safeUser } = user;
console.log(safeUser); // { id: 1, name: 'Alice' }
5. 嵌套对象解构
当对象中包含嵌套的子对象时,可以通过“多层解构”直接提取子对象的属性。语法上,在对应位置使用嵌套的花括号,按层级匹配属性。
const obj = {
info: { name: 'Charlie', age: 35 }, // 嵌套的info对象
score: 90
};
// 第一层解构:提取info属性和score属性
// 第二层解构:从info对象中提取name属性
const { info: { name }, score } = obj;
console.log(name); // 'Charlie'(成功提取嵌套属性)
console.log(score); // 90(提取外层属性)
如果需要同时保留外层对象和内层属性,可以单独定义外层变量:
// 同时保留info对象和其内部的name属性
const { info, info: { name } } = obj;
console.log(info); // { name: 'Charlie', age: 35 }(外层对象)
console.log(name); // 'Charlie'(内层属性)
三、其他场景的解构
除了数组和对象的基础解构,解构语法还可以应用于函数参数、字符串等场景,进一步简化代码。
1. 函数参数解构
当函数接收一个数组或对象作为参数时,可以直接在参数列表中使用解构语法,快速提取所需的值,避免在函数内部重复编写 参数.属性 或 参数[索引]。
-
对象参数解构:适合参数为对象的场景,直接提取所需属性,还可结合默认值处理参数缺失的情况。
// 函数参数中直接解构对象,提取name和age属性,并为age设置默认值18 function printUser({ name, age = 18 }) { console.log(`Name: ${name}, Age: ${age}`); } // 调用时只需传入包含name的对象,age会使用默认值 printUser({ name: 'Dave' }); // Name: Dave, Age: 18注意:若函数可能被无参数调用,需为解构参数设置默认空对象
= {},否则会报错:// 错误示例:无参数调用时,解构undefined会报错 function fn({ a }) {} fn(); // TypeError: Cannot destructure property 'a' of 'undefined' or 'null' // 正确示例:设置默认空对象,避免报错 function fn({ a } = {}) { console.log(a); // undefined(安全处理) } fn(); -
数组参数解构:适合参数为数组的场景,按位置提取元素,使函数逻辑更清晰。
// 函数参数中解构数组,直接获取a和b function sum([a, b]) { return a + b; } console.log(sum([2, 3])); // 5(无需在函数内写arr[0]和arr[1])
2. 字符串解构
字符串是类数组对象(具有长度和索引),因此可以像数组一样被解构,按索引位置提取字符。
// 字符串'hello'按索引0、1、2的位置提取字符
const [c1, c2, c3] = 'hello';
console.log(c1); // 'h'(索引0的字符)
console.log(c2); // 'e'(索引1的字符)
console.log(c3); // 'l'(索引2的字符)
结合剩余操作符,还可以提取前几个字符和剩余字符:
const [first, ...rest] = 'hello';
console.log(first); // 'h'
console.log(rest); // ['e', 'l', 'l', 'o'](剩余字符组成的数组)
3. 解构与展开(Spread)的区别
... 符号在 JavaScript 中既可以用于解构(剩余操作符),也可以用于展开(Spread 操作符),但两者的作用完全相反:
- 解构(剩余操作符):从数组或对象中提取部分值,并将剩余值收集到一个新的数组或对象中(拆包过程)。
- 展开(Spread 操作符):将数组或对象中的所有值展开到另一个数组、对象或函数参数中(打包过程)。
示例对比:
// 解构(拆包):从数组中提取first,剩余元素收集到rest
const [first, ...rest] = [1, 2, 3];
console.log(first); // 1
console.log(rest); // [2, 3]
// 展开(打包):将rest数组的元素展开到新数组中
const newArr = [...rest, 4];
console.log(newArr); // [2, 3, 4](rest的元素被展开后添加4)
解构语法在实际开发中应用非常广泛,能极大简化代码逻辑。以下是一些常见的应用场景及示例:
四、常见应用场景
1. 处理函数参数
- 提取对象参数的特定属性
当函数接收一个对象作为参数时,直接解构出需要的属性,避免重复写 obj.xxx:
// 传统写法
function getUserInfo(user) {
console.log(user.name, user.age, user.gender);
}
// 解构写法(更简洁)
function getUserInfo({ name, age, gender = '未知' }) {
console.log(name, age, gender);
}
getUserInfo({ name: '张三', age: 20 }); // 张三 20 未知
- 处理数组参数
适合需要按顺序提取数组中特定位置的值的场景:
// 计算二维坐标距离
function getDistance([x1, y1], [x2, y2]) {
return Math.hypot(x2 - x1, y2 - y1);
}
console.log(getDistance([0, 0], [3, 4])); // 5(3-4-5直角三角形斜边)
2. 交换变量值
无需临时变量,一行代码完成变量交换,常用于排序、算法等场景:
let a = 10, b = 20;
// 传统交换(需要临时变量)
let temp = a;
a = b;
b = temp;
// 解构交换(更简洁)
[a, b] = [b, a];
console.log(a, b); // 20 10
3. 处理数组返回值
函数返回多个值时,通常用数组包裹,解构可直接按位置接收:
// 函数返回数组(多值)
function getMinMax(arr) {
return [Math.min(...arr), Math.max(...arr)];
}
const numbers = [3, 1, 5, 2];
const [min, max] = getMinMax(numbers);
console.log(min, max); // 1 5
4. 设置函数默认参数
结合默认值,处理函数参数缺失的情况:
// 未使用解构时,默认参数写法繁琐
function config(options) {
const timeout = options.timeout || 5000;
const method = options.method || 'GET';
}
// 解构+默认值(简洁且直观)
function config({ timeout = 5000, method = 'GET' } = {}) {
console.log(timeout, method);
}
config(); // 5000 GET(参数为空时使用默认值)
config({ method: 'POST' }); // 5000 POST
注意:
= {}是为了避免参数为undefined时解构报错(比如直接调用config())。
5. 遍历对象/数组时提取值
在 for...of 或数组方法(如 map、forEach)中,快速提取数据:
// 遍历数组对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
users.forEach(({ id, name }) => {
console.log(`ID: ${id}, Name: ${name}`);
});
// 遍历Map(Map的entries是[key, value]形式)
const map = new Map();
map.set('name', 'Charlie');
map.set('age', 30);
for (const [key, value] of map) {
console.log(`${key}: ${value}`); // name: Charlie; age: 30
}
6. 忽略不需要的数据
从数组或对象中提取部分值,忽略无关数据,减少内存占用:
// 数组中只需要第一个和最后一个元素
const [first, , , last] = [10, 20, 30, 40];
console.log(first, last); // 10 40
// 对象中只需要部分属性
const { password, ...userInfo } = { name: 'Dave', age: 28, password: '123456' };
console.log(userInfo); // { name: 'Dave', age: 28 }(不含password)
7. 嵌套结构数据处理
面对多层嵌套的对象/数组,解构能一次性提取深层数据:
const data = {
result: {
items: [
{ id: 1, details: { price: 100 } },
{ id: 2, details: { price: 200 } }
]
}
};
// 提取第一个item的price
const { result: { items: [firstItem] } } = data;
const { details: { price } } = firstItem;
console.log(price); // 100
总结
解构语法通过“按位置匹配”(数组)和“按属性名匹配”(对象)的方式,简化了从复杂数据结构中提取值的过程,减少重复代码、提升可读性在数据存在缺失值和多余数据的情况下能灵活处理边界情况。其核心优势是简化数据提取流程,尤其在以下场景中表现突出:
- 处理函数参数(减少
obj.xxx重复调用)。 - 变量交换、多值返回处理(减少临时变量)。
- 遍历集合时提取关键信息(让循环体更简洁)。
喜欢的话,留下你的评论吧~