跳到主要内容

解构赋值

解构赋值做了什么?​

  • 语义: 把数组或对象"拆包"到一组变量;
  • 非破坏性: 只是把元素复制进变量, 原数组/原对象不会被修改;
  • 数组版: 按位置取值, let [firstName, surname] = arr 等价于分两行逐个下标赋值;
  • 对象版: 按属性名取值, 与左侧书写顺序无关;
  • 右侧要求: 数组解构接受任何可迭代对象, 内部就是对着右侧跑一趟 for...of;
  • 左侧要求: 任何"可赋值目标"都行, 包括已有变量与对象属性;
let [firstName, surname] = "John Smith".split(" ");
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two] = new Set([1, 2]);

const user = {};
[user.name, user.surname] = "John Smith".split(" ");
  • 常见来源: split()、Object.entries()、Map 迭代器等返回数组的接口;

数组解构有哪些细节?​

  • 忽略元素: 用多余逗号跳过, let [firstName, , title] = arr 只取第 1 与第 3 项;
  • 取值不足: 数组比左侧短时不报错, 缺失项为 undefined;
  • 默认值: 写成 = value, 仅在对应位置为 undefined 时生效;
  • 惰性求值: 默认值可以是任意表达式或函数调用, 只在需要时才求值;
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
// name = "Julius", surname = "Anonymous"

let [name2 = prompt("name?"), surname2 = prompt("surname?")] = ["Julius"];
// 只会弹出 surname 的输入框
  • 交换变量: 临时造一个数组再换序解构, [guest, admin] = [admin, guest], 参数更多也适用;
  • 剩余元素: ...rest 收集剩余项为新数组, 且必须放在最后;
let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "Imperator"];
// titles = ["Consul", "Imperator"]

对象解构如何重命名与取默认值?​

  • 基本: let {title, width} = options 把同名属性绑到同名变量;
  • 重命名: {sourceProperty: targetVariable}, 冒号表示"哪个属性给哪个变量", {width: w} 把 options.width 绑到 w;
  • 默认值: 同一位置可写 {width: w = 100}, 重命名与默认值可组合;
  • 触发条件: 属性缺失或值为 undefined 时用默认值, 同样支持复杂表达式;
  • 剩余属性: ...rest 收集所有未被映射的属性, 结果为新对象;
  • 完整形态: let {prop: varName = defaultValue, ...rest} = object;
  • 只取所需: 对象属性多时只列需要的键, 其余忽略;
let options = { title: "Menu", width: 100 };
let { width: w = 100, height: h = 200, ...rest } = options;
// w = 100, h = 200, rest = { title: "Menu" }

嵌套解构怎么写?​

  • 规则: 左侧模式与右侧结构同形即可, 对象套数组、数组套对象都支持;
  • 结构层不产变量: size: {width} 只得到 width, 没有 size 这个变量;
  • 缺失兜底: 嵌套位置也可写默认值;
let options = {
size: { width: 100, height: 200 },
items: ["Cake", "Donut"],
};
let {
size: { width, height },
items: [item1, item2],
title = "Menu", // 对象里没有, 走默认值
} = options;
// width=100, height=200, item1="Cake", item2="Donut", title="Menu"

函数参数解构解决了什么问题?​

  • 痛点: 参数多且大多可选时, 调用方必须记住顺序, 还要用 undefined 占位;
  • 写法: 形参位置直接写解构模式, 实参传一个配置对象, 顺序无关且自解释;
  • 默认值分工: 模式内默认值处理"缺属性", 参数默认 = {} 处理"完全不传参";
  • 注意: 不写 = {} 时 showMenu() 会报错, 因为没有对象可以解构;
function showMenu({ title = "Untitled", width = 200, items: [i1, i2] } = {}) {
return `${title} ${width} ${i1}/${i2}`;
}

showMenu(); // 全部走默认值
showMenu({}); // 同上

循环里如何配合解构?​

  • Object.entries: 返回 [key, value] 数组, 可直接解构遍历键值;
  • Map 迭代: 迭代项本身就是 [key, value], 解构更直接;
for (const [key, value] of Object.entries({ name: "John", age: 30 })) {
console.log(`${key}:${value}`);
}

const map = new Map([["name", "John"]]);
for (const [key, value] of map) console.log(`${key}:${value}`);
  • 范围: 对象解构只取对象自身的可枚举属性;

不用 let 解构为什么会报错?​

  • 原因: 主代码流中语句以 { 开头会被当作代码块, 而不是解构模式;
  • 修法: 用括号把整个赋值包成表达式;
let title, width;
{title, width} = {title: "Menu", width: 200}; // 报错: 被当成代码块
({title, width} = {title: "Menu", width: 200}); // 正确