展开运算符(...)浅拷贝存在可测量性能损耗,因其需枚举属性、逐个赋值,并触发隐藏类重建等开销;在10万次拷贝100属性对象测试中比object.assign慢约6–7ms,但多数场景下微秒级损耗无需优化。

展开运算符(...)在浅拷贝对象时确实存在可测量的性能损耗,尤其在对象属性较多或高频调用场景下不可忽略。它本质是语法糖,底层仍需遍历键值对、创建新对象、逐个赋值,而非底层内存复制。
展开运算符的执行过程决定性能上限
使用 {...obj} 并非原子操作:JavaScript 引擎需先枚举对象自身可枚举属性(按属性添加顺序或引擎优化后的顺序),再逐个读取值、写入新对象。这个过程涉及:
- 属性键的枚举开销(尤其当对象有大量自有属性时)
- 每个属性的 get 操作(若定义了 getter,还会触发副作用)
- 新对象的动态属性分配(V8 中可能触发隐藏类重建)
- 无类型推断——即使源对象结构稳定,每次展开仍需运行时判断
与替代方案的典型性能对比(以 V8 为例)
在 Chrome 120+ 环境下,拷贝含 100 个字符串属性的对象(无 getter/setter),10 万次循环平均耗时参考:
-
{...obj}:约 32–38 ms -
Object.assign({}, obj):约 26–31 ms(略快,因内部高度优化且跳过部分语法层检查) -
structuredClone(obj)(仅支持可序列化值):约 85–120 ms(深拷贝开销大,不具可比性) - 手动
const copy = {a: obj.a, b: obj.b, ...}:约 8–12 ms(编译期确定路径,无枚举)
差距随属性数量线性扩大;若含 Symbol 属性或不可枚举属性,{...obj} 会自动忽略,而 Object.getOwnPropertyDescriptors + Object.defineProperties 可精确控制但更慢。
实际项目中是否需要优化?
多数业务逻辑中单次展开损耗在微秒级,人眼和交互完全无感。是否值得优化取决于:
- 是否在热路径中(如 requestAnimationFrame 循环、高频事件处理器、数据流 pipeline)
- 对象规模是否稳定大于 50 个自有属性
- 是否已通过 Performance API 或 DevTools 的 Performance 面板确认该操作是瓶颈
- 代码可维护性是否因过度优化受损(例如硬编码字段列表难以同步变更)
建议优先保障语义清晰和可读性;当 profiling 明确指向展开操作且优化收益显著时,再考虑降级为 Object.assign 或定制化浅拷贝函数。
小结:不是“慢”,而是“有成本”
展开运算符的简洁性和表达力远超其微小性能代价。它带来的损耗真实存在,但属于合理设计权衡的一部分。与其盲目替换,不如理解其边界——它适合开发期快速原型、配置合并、props 透传等场景;不适合实时音频处理、游戏帧逻辑或百万级数据映射中的内层循环。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











