ES6 引入的解构赋值(Destructuring Assignment)和展开运算符(Spread Operator)从根本上改变了 JavaScript 中提取和组合数据的方式。它们让原本需要多行代码完成的操作浓缩为简洁的表达式,显著提升了代码的可读性和编写效率。

数组解构

数组解构按照位置顺序从数组中提取值,赋值给对应的变量:

const colors = ['#ff0000', '#00ff00', '#0000ff'];

// 传统写法
const red = colors[0];
const green = colors[1];

// 解构写法
const [primary, secondary, tertiary] = colors;

console.log(primary);   // '#ff0000'
console.log(secondary); // '#00ff00'

你可以跳过不需要的元素,使用逗号留空即可:

const [, , blue] = colors;
console.log(blue); // '#0000ff'

对象解构

对象解构根据属性名匹配来提取值,这是日常开发中使用频率最高的解构方式:

const user = {
  name: '张三',
  age: 28,
  email: 'zhangsan@example.com'
};

const { name, age, email } = user;
console.log(name);  // '张三'
console.log(age);   // 28

如果希望变量名与属性名不同,可以使用冒号语法重命名:

const { name: userName, age: userAge } = user;
console.log(userName); // '张三'

默认值

当解构的值为 undefined 时,默认值会生效:

const settings = { theme: 'dark' };

const { theme, language = 'zh-CN', fontSize = 14 } = settings;
console.log(language); // 'zh-CN'(使用默认值)
console.log(fontSize); // 14(使用默认值)
console.log(theme);    // 'dark'(使用实际值)

嵌套解构

解构可以深入多层嵌套的对象和数组结构:

const response = {
  status: 200,
  data: {
    user: {
      id: 1,
      profile: {
        avatar: 'avatar.png',
        nickname: '小明'
      }
    }
  }
};

const {
  data: {
    user: {
      profile: { nickname, avatar }
    }
  }
} = response;

console.log(nickname); // '小明'
console.log(avatar);   // 'avatar.png'

嵌套解构虽然强大,但层数过深会降低代码可读性。在实际项目中,建议解构不超过两层,更深的层级可以分步处理。

Rest 模式

使用三个点(...)配合解构可以收集剩余的元素,这被称为 Rest 模式:

// 数组 rest
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first);  // 1
console.log(rest);   // [3, 4, 5]

// 对象 rest
const { name, ...others } = { name: '张三', age: 28, city: '北京' };
console.log(others); // { age: 28, city: '北京' }

展开运算符

展开运算符同样使用 ... 语法,但作用是"展开"一个可迭代对象。与 Rest 模式相反:Rest 是收集,Spread 是展开。

数组展开

const a = [1, 2, 3];
const b = [4, 5, 6];

// 合并数组
const merged = [...a, ...b];
console.log(merged); // [1, 2, 3, 4, 5, 6]

// 在任意位置插入元素
const inserted = [0, ...a, 3.5, ...b, 7];
console.log(inserted); // [0, 1, 2, 3, 3.5, 4, 5, 6, 7]

// 浅拷贝数组
const copy = [...a];
copy.push(4);
console.log(a);    // [1, 2, 3](原数组不变)
console.log(copy); // [1, 2, 3, 4]

对象展开

const defaults = {
  theme: 'light',
  language: 'zh-CN',
  fontSize: 14
};

const userPrefs = {
  theme: 'dark',
  fontSize: 16
};

// 合并对象,后面的属性覆盖前面的
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: 'dark', language: 'zh-CN', fontSize: 16 }

对象展开的顺序很重要:后面的同名属性会覆盖前面的。利用这个特性,可以很方便地实现默认值合并。

函数参数解构

解构赋值在函数参数中极为常用,尤其是处理配置对象时:

// 传统写法:手动提取,冗长且容易遗漏
function createUser(options) {
  const name = options.name || '匿名';
  const age = options.age || 0;
  const role = options.role || 'user';
  // ...
}

// 解构写法:简洁且自带默认值
function createUser({ name = '匿名', age = 0, role = 'user', email } = {}) {
  console.log(`${name}, ${role}, ${age}岁`);
  if (email) console.log(`邮箱: ${email}`);
}

createUser({ name: '张三', age: 28, email: 'z@test.com' });
// 张三, user, 28岁
// 邮箱: z@test.com

createUser();
// 匿名, user, 0岁

注意函数签名末尾的 = {},它确保不传参数时不会报错——解构 undefined 会抛出 TypeError,而解构空对象则正常使用默认值。

实际应用场景

在日常开发中,解构和展开运算符的身影无处不在。React 组件的 props 解构、Redux 中的 state 合并、API 响应数据的提取、函数配置参数的处理,都大量使用了这些语法。掌握它们不仅能让你写出更简洁的代码,还能帮助你更好地理解现代 JavaScript 框架和库的源码。

一个值得注意的细节是,展开运算符只做浅拷贝。如果对象中嵌套了引用类型,展开后内外层的引用仍然指向同一个对象。需要深拷贝时,应使用 structuredClone() 或其他深拷贝方案。