扩展运算符只做浅拷贝,嵌套对象引用会被复用导致属性污染;基本类型安全,嵌套引用类型需手动深拷贝或结构化克隆;推荐扁平化配置、不可变管理或合并后冻结。

扩展运算符({...obj})在合并配置对象时确实简洁高效,但它只做浅拷贝——嵌套对象的引用会被直接复用,一旦修改深层属性,原始配置就可能被意外改写,也就是“属性污染”。防范的关键不是避免用它,而是清楚它在哪一层失效,并针对性补位。
明确哪些属性会引发污染
污染只发生在嵌套的引用类型值上:比如 { theme: 'dark', user: { id: 1, profile: { name: 'A' } } } 中,user 和 profile 都是对象,用扩展运算符合并后,finalConfig.user.profile 和原对象指向同一内存地址。只要后续代码执行了 finalConfig.user.profile.name = 'B',原始配置里的 profile.name 也会变。
而顶层的字符串、数字、布尔值等基本类型完全安全,不会相互影响。
对已知嵌套结构做手动深拷贝
如果配置结构固定且嵌套层级少,推荐在合并前对关键嵌套字段单独处理:
- 用
JSON.parse(JSON.stringify())快速克隆简单嵌套对象(不含函数、undefined、Date、RegExp 等) - 对特定字段使用结构化克隆,例如:
const finalConfig = { ...defaultConfig, ...userConfig, user: { ...defaultConfig.user, ...userConfig.user } }; - 若嵌套中含数组,也需单独展开:
filters: [...defaultConfig.filters, ...userConfig.filters]
用 Object.assign + 深拷贝工具兜底
当配置结构复杂或不确定时,不建议仅靠扩展运算符。可组合使用:
-
Object.assign负责顶层合并逻辑(支持多源、覆盖顺序清晰) - 配合成熟深拷贝库如
lodash.cloneDeep或浏览器原生structuredClone(现代环境可用)处理整个结果:const finalConfig = structuredClone(Object.assign({}, defaultConfig, userConfig)); - 注意:不要对原始默认配置直接深拷贝再合并,应先合并再深拷贝最终结果,避免重复克隆
配置设计阶段就规避深层嵌套
从源头降低风险更有效:
- 把嵌套配置扁平化,例如将
api.baseURL拆成apiBaseURL; - 用不可变方式管理配置:每次更新都返回新对象,配合
immer等库让深层修改更可控; - 在合并后加一层校验或冻结(
Object.freeze),防止运行时误改(适用于静态配置)。
浅拷贝不是缺陷,是设计权衡。理解它在哪起作用、在哪失效,比追求“全自动深拷贝”更实际。真正要防的,是那些你以为改了不影响、其实已经牵一发而动全身的地方。


















