浅拷贝只复制对象第一层属性值,对引用类型仅复制内存地址,导致嵌套对象仍共享同一引用;ESM中导入的是绑定而非快照,嵌套属性修改会跨模块即时可见,但替换整个导出对象不会自动传播。

浅拷贝在 JavaScript 模块内部不会自动传播引用变更,它只复制对象第一层的属性值;如果原对象的属性是引用类型(如对象、数组、函数),拷贝后的新对象仍与原对象共享这些内部引用。
浅拷贝只切断顶层引用
模块中使用 Object.assign()、展开运算符 {...obj} 或 Array.from(arr) 等方式做浅拷贝时,仅对对象自身可枚举属性做一次赋值。这意味着:
- 原始对象和拷贝对象的顶层属性互不影响(比如修改
copy.a = 2不会影响original.a) - 但若
original.b是一个对象,copy.b和original.b指向同一内存地址,任一方修改其属性都会反映到另一方 - 模块导出的是拷贝后的对象,其他模块导入该对象时,拿到的仍是这个“浅拷贝体”,而非原始源
模块作用域下引用传播的实际表现
假设模块 A 导出一个被浅拷贝的对象,模块 B 和 C 同时导入它:
- 模块 B 修改了该对象的嵌套属性(如
shared.config.theme.color = 'red'),模块 C 立即可见该变化——因为它们引用的是同一个嵌套对象 - 但如果模块 B 替换整个导出对象(
export const config = {...oldConfig}),模块 C 不会自动更新,除非它重新导入或监听变更 - ESM 的绑定是只读的(
export const obj = {...}),直接赋值会报错;但嵌套属性修改不受限制
避免意外共享的常见做法
当模块间需独立操作配置或状态时,应主动切断深层引用:
立即学习“Java免费学习笔记(深入)”;
- 需要完全隔离时,用深拷贝(如
structuredClone(),或 JSON 序列化+反序列化,注意函数/undefined/Date 等受限) - 模块内封装创建逻辑,导出工厂函数(
export const createConfig = () => ({...defaultConfig})),让每个消费者获得独立副本 - 对可变嵌套结构使用
Object.freeze()或不可变库(如 immer 的produce),从设计上防止误改
静态导入与运行时引用的关系
ES 模块的导入绑定在解析时建立,指向模块作用域内的变量绑定,不是值的快照:
- 即使你浅拷贝后导出,其他模块 import 的仍是该绑定所指向的值——若该值后续被重新赋值(如
config = {...config}),且模块未用const声明,则新值会传播到所有导入处 - 但浅拷贝本身不触发传播;传播只发生在模块内部变量被重新赋值,且该变量是导出绑定的目标时
- 因此,“引用传播”本质是模块绑定机制所致,不是浅拷贝的功能,浅拷贝只是没阻止底层共享


















