JavaScript 展开运算符(Spread Operator)完全指南
📅 2026/7/7 2:10:37
👁️ 阅读次数
📝 编程学习
JavaScript 展开运算符(Spread Operator)完全指南
深入理解
...的语法、行为和各种边界情况
目录
- 一、基础概念
- 二、对象展开
- 三、数组展开
- 四、条件展开详解
- 五、对象 vs 数组的行为差异
- 六、常见坑点与最佳实践
- 七、完整参考表
一、基础概念
展开运算符(...)允许一个表达式在期望多个元素(数组)或多个参数(函数调用)或多个属性(对象)的位置展开。
两种使用场景
javascript
// 1. 对象展开 - 复制对象属性 const obj = { ...source }; // 2. 数组展开 - 展开可迭代元素 const arr = [...source];二、对象展开
2.1 基本用法
javascript
// 复制对象 const source = { name: '张三', age: 18 }; const copy = { ...source }; // 结果:{ name: '张三', age: 18 } // 合并对象 const obj1 = { a: 1, b: 2 }; const obj2 = { c: 3, d: 4 }; const merged = { ...obj1, ...obj2 }; // 结果:{ a: 1, b: 2, c: 3, d: 4 } // 覆盖属性(后面的覆盖前面的) const base = { name: '张三', age: 18 }; const update = { age: 20, city: '北京' }; const result = { ...base, ...update }; // 结果:{ name: '张三', age: 20, city: '北京' }2.2 各种值的展开行为
javascript
// ✅ 普通对象 const obj = { name: '张三' }; { ...obj } // { name: '张三' } // ✅ 空对象 const empty = {}; { ...empty } // {} // ✅ 数组展开为对象(索引变属性名) const arr = [1, 2, 3]; { ...arr } // { '0': 1, '1': 2, '2': 3 } // ✅ 字符串展开为对象 const str = 'abc'; { ...str } // { '0': 'a', '1': 'b', '2': 'c' } // ✅ null 和 undefined(被忽略) { ...null } // {} (不报错) { ...undefined } // {} (不报错) // ✅ 数字和布尔值 { ...123 } // {} (数字没有可枚举属性) { ...true } // {} (布尔没有可枚举属性) // ❌ 注意:在对象字面量外部不能直接展开 // ...null; // 语法错误2.3 对象展开的规范行为
根据 ECMAScript 规范,对象展开时:
null或undefined:直接跳过(不处理)- 原始类型(
string、number、boolean):先包装成对象,再复制可枚举属性 - 普通对象:复制所有可枚举属性
javascript
// 规范示例 const obj = { ...null, // 跳过 ...undefined, // 跳过 ...'abc', // { '0': 'a', '1': 'b', '2': 'c' } ...123, // {} (无属性) ...true, // {} (无属性) ...{ x: 1 } // { x: 1 } }; // 最终结果:{ '0': 'a', '1': 'b', '2': 'c', x: 1 }三、数组展开
3.1 基本用法
javascript
// 复制数组 const source = [1, 2, 3]; const copy = [...source]; // 结果:[1, 2, 3] // 合并数组 const arr1 = [1, 2]; const arr2 = [3, 4]; const merged = [...arr1, ...arr2]; // 结果:[1, 2, 3, 4] // 添加元素 const arr = [1, 2]; const newArr = [0, ...arr, 3]; // 结果:[0, 1, 2, 3]3.2 各种值的展开行为
javascript
// ✅ 普通数组 const arr = [1, 2, 3]; [...arr] // [1, 2, 3] // ✅ 空数组 const empty = []; [...empty] // [] // ✅ 字符串(可迭代) const str = 'abc'; [...str] // ['a', 'b', 'c'] // ✅ Set(可迭代) const set = new Set([1, 2, 3]); [...set] // [1, 2, 3] // ✅ Map(可迭代,返回键值对数组) const map = new Map([['a', 1], ['b', 2]]); [...map] // [['a', 1], ['b', 2]] // ❌ null 和 undefined(报错) [...null] // TypeError: null is not iterable [...undefined] // TypeError: undefined is not iterable // ❌ 普通对象(不可迭代) const obj = { name: '张三' }; [...obj] // TypeError: obj is not iterable // ❌ 数字(不可迭代) [...123] // TypeError: 123 is not iterable // ❌ 布尔值(不可迭代) [...true] // TypeError: true is not iterable3.3 数组展开的要求
数组展开要求值必须是可迭代对象(Iterable):
- ✅ Array
- ✅ String
- ✅ Set
- ✅ Map
- ✅ NodeList(DOM 元素集合)
- ✅ arguments 对象
- ❌ 普通对象
- ❌ null / undefined
- ❌ Number / Boolean
四、条件展开详解
4.1 核心语法
javascript
...(condition && { data })这是短路求值+展开运算符的组合使用。
4.2 执行步骤
javascript
// 代码 ...(data && { data }) // 步骤1:计算 data && { data } // 如果 data 是真值 → 返回 { data } // 如果 data 是假值 → 返回 data 本身 // 步骤2:展开步骤1的结果 // 如果是对象 → 展开属性 // 如果是假值(null/undefined)→ 在对象中忽略4.3 各种值的行为
| data 值 | data && { data }结果 | 展开结果 |
|---|---|---|
{ id: 1 } | { data: { id: 1 } } | ✅ 添加 data 字段 |
[1, 2, 3] | { data: [1, 2, 3] } | ✅ 添加 data 字段 |
'hello' | { data: 'hello' } | ✅ 添加 data 字段 |
true | { data: true } | ✅ 添加 data 字段 |
null | null | ⏭️ 被忽略 |
undefined | undefined | ⏭️ 被忽略 |
0 | 0 | ⏭️ 被忽略(⚠️ 可能是问题) |
'' | '' | ⏭️ 被忽略(⚠️ 可能是问题) |
false | false | ⏭️ 被忽略(⚠️ 可能是问题) |
NaN | NaN | ⏭️ 被忽略(⚠️ 可能是问题) |
4.4 实际应用示例
javascript
// 当前代码 function sendJsonResponse(res, statusCode, message, data = null) { return res.status(statusCode).json({ message, ...(data && { data }) }); } // 测试 sendJsonResponse(res, 200, '成功', { id: 1 }); // 返回:{ message: '成功', data: { id: 1 } } sendJsonResponse(res, 200, '成功', null); // 返回:{ message: '成功' } ✅ 干净,没有 data: null sendJsonResponse(res, 200, '成功', 0); // 返回:{ message: '成功' } ⚠️ 但你可能想要 data: 04.5 ⚠️ 注意:假值陷阱
javascript
// 问题:0、''、false 是有效数据,但会被当作"无数据" const data = 0; ...(data && { data }) // 不会添加 data 字段 // 解决方案1:只排除 null 和 undefined ...(data != null && { data }) // 或 ...(data !== null && data !== undefined && { data }) // 解决方案2:使用空值合并运算符 ...(data ?? { data }) // 注意:这会在 data 为 null/undefined 时添加 {} // 但这样会添加空对象,不推荐五、对象 vs 数组的行为差异
5.1 核心区别
| 特性 | 对象展开{...} | 数组展开[...] |
|---|---|---|
| null 和 undefined | ✅ 被忽略,不报错 | ❌ 报错(不可迭代) |
| 普通对象 | ✅ 复制属性 | ❌ 报错(不可迭代) |
| 数组 | ✅ 索引变属性名 | ✅ 展开元素 |
| 字符串 | ✅ 索引变属性名 | ✅ 展开字符 |
| 数字/布尔 | ✅ 无属性,不报错 | ❌ 报错(不可迭代) |
| Set/Map | ❌ 报错(普通对象行为) | ✅ 展开元素 |
5.2 对比示例
javascript
// 对象展开 - 宽容 const objResult = { ...null, // ✅ 跳过 ...undefined, // ✅ 跳过 ...'abc', // ✅ { '0': 'a', '1': 'b', '2': 'c' } ...[1, 2], // ✅ { '0': 1, '1': 2 } ...123, // ✅ {} }; // 结果:{ '0': 1, '1': 2, '2': 'c' } // 数组展开 - 严格 try { const arrResult = [ ...null, // ❌ TypeError ...undefined, // ❌ TypeError ...'abc', // ✅ 'a', 'b', 'c' ...[1, 2], // ✅ 1, 2 ...123, // ❌ TypeError ]; } catch (e) { console.log('数组展开报错!'); }5.3 安全的数组条件展开
javascript
// 方案1:使用空数组默认值 const arr = [...(data ?? [])]; // 方案2:先判断再展开 const arr = data ? [...data] : []; // 方案3:Array.from(更安全) const arr = Array.from(data ?? []); // 方案4:concat const arr = [].concat(data ?? []); // 方案5:条件判断 const arr = []; if (data && Array.isArray(data)) { arr.push(...data); }六、常见坑点与最佳实践
6.1 坑点清单
❌ 坑1:假值被忽略
javascript
// 问题 const data = 0; ...(data && { data }) // data 字段丢失 // 解决 ...(data != null && { data })❌ 坑2:数组展开 null
javascript
// 问题 const arr = [...data]; // data 为 null 时报错 // 解决 const arr = [...(data ?? [])];❌ 坑3:对象展开非普通对象
javascript
// 问题 const obj = { ...new Date() }; // 会复制日期对象的所有属性 // 结果:{ toString: [Function], toDateString: [Function], ... } // 解决:明确只复制需要的属性 const { year, month } = new Date();❌ 坑4:浅拷贝问题
javascript
// 问题:展开只做浅拷贝 const original = { user: { name: '张三' } }; const copy = { ...original }; copy.user.name = '李四'; console.log(original.user.name); // '李四'(被修改了!) // 解决:深拷贝 const deepCopy = JSON.parse(JSON.stringify(original)); // 或使用 structuredClone(Node 17+) const clone = structuredClone(original);6.2 最佳实践
✅ 推荐1:条件展开只排除 null/undefined
javascript
// 好的做法 ...(data != null && { data }) // 而不是 ...(data && { data })✅ 推荐2:数组展开使用空值合并
javascript
// 好的做法 const arr = [...(items ?? [])]; // 而不是 const arr = [...items]; // items 为 null 时报错✅ 推荐3:对象展开保持纯净
javascript
// 好的做法:使用展开创建新对象 const updateUser = (user, updates) => ({ ...user, ...updates, updatedAt: new Date() }); // 而不是:直接修改原对象 const updateUser = (user, updates) => { Object.assign(user, updates); // ❌ 修改了原对象 return user; };✅ 推荐4:使用展开进行不可变更新
javascript
// 添加属性 const newState = { ...state, newProp: 'value' }; // 更新属性 const newState = { ...state, prop: 'newValue' }; // 删除属性 const { deleteProp, ...newState } = state; // 更新嵌套属性 const newState = { ...state, user: { ...state.user, age: 20 } };七、完整参考表
7.1 对象展开参考表
| 值 | 示例 | 结果 | 是否报错 |
|---|---|---|---|
| 普通对象 | { ...{a:1} } | { a: 1 } | ✅ 否 |
| 空对象 | { ...{} } | {} | ✅ 否 |
| 数组 | { ...[1,2] } | { '0': 1, '1': 2 } | ✅ 否 |
| 字符串 | { ...'abc' } | { '0': 'a', '1': 'b', '2': 'c' } | ✅ 否 |
| null | { ...null } | {} | ✅ 否 |
| undefined | { ...undefined } | {} | ✅ 否 |
| 数字 | { ...123 } | {} | ✅ 否 |
| 布尔值 | { ...true } | {} | ✅ 否 |
| Set | { ...new Set() } | {} | ✅ 否 |
| Date | { ...new Date() } | 复制所有属性 | ✅ 否 |
7.2 数组展开参考表
| 值 | 示例 | 结果 | 是否报错 |
|---|---|---|---|
| 数组 | [...[1,2]] | [1, 2] | ✅ 否 |
| 空数组 | [...[]] | [] | ✅ 否 |
| 字符串 | [...'abc'] | ['a', 'b', 'c'] | ✅ 否 |
| Set | [...new Set([1,2])] | [1, 2] | ✅ 否 |
| Map | [...new Map([['a',1]])] | [['a', 1]] | ✅ 否 |
| null | [...null] | - | ❌ TypeError |
| undefined | [...undefined] | - | ❌ TypeError |
| 普通对象 | [...{a:1}] | - | ❌ TypeError |
| 数字 | [...123] | - | ❌ TypeError |
| 布尔值 | [...true] | - | ❌ TypeError |
7.3 条件展开参考表
| 条件值 | 表达式 | 对象展开结果 | 数组展开结果 |
|---|---|---|---|
{ data: 1 } | ...(value && { data }) | ✅ 添加 data | ❌ 报错 |
null | ...(value && { data }) | ⏭️ 忽略 | ❌ 报错 |
undefined | ...(value && { data }) | ⏭️ 忽略 | ❌ 报错 |
0 | ...(value && { data }) | ⏭️ 忽略 | ❌ 报错 |
'' | ...(value && { data }) | ⏭️ 忽略 | ❌ 报错 |
false | ...(value && { data }) | ⏭️ 忽略 | ❌ 报错 |
八、总结
- 对象展开对
null/undefined宽容,在对象字面量中会被静默忽略 - 数组展开要求值必须可迭代,
null/undefined会直接报错 - 条件展开
...(data && { data })是一种优雅的条件添加属性的方式 - 注意假值陷阱:
0、''、false会被当作"无数据" - 推荐使用
data != null来精确判断是否添加字段
javascript
// 最终推荐写法 function sendJsonResponse(res, statusCode, message, data = null) { return res.status(statusCode).json({ message, ...(data != null && { data }) // 只有 null/undefined 时不添加 }); }参考资料
- MDN: Spread syntax
- ECMAScript Specification
- JavaScript 展开运算符的深度解析
📝 本文由 [小新1688] 整理,欢迎分享和讨论!
如果觉得有用,请给个 Star ⭐ 或分享给更多开发者~
编程学习
技术分享
实战经验