闭包本身不直接创建不可变对象,但通过封装私有状态、阻断外部修改、仅暴露返回新对象的接口来支撑不可变模式;外层函数隐藏状态,内层函数只提供基于当前状态生成新对象的方法,确保原对象始终不变。

JavaScript 中闭包本身并不直接创建不可变对象,但它能为实现不可变对象模式提供关键支撑——通过封装状态、阻断外部直接修改,并仅暴露受控的“衍生新状态”接口。
闭包如何支持不可变对象的核心逻辑
不可变对象要求:一旦创建,其内部状态不能被更改;任何“修改”操作都必须返回一个新对象。闭包不改变这个原则,而是帮我们安全地隐藏可变状态,并确保所有对外输出都是新构造的对象,而非对原状态的篡改。
- 外层函数定义私有数据(如原始值、对象引用或配置),该数据不会暴露给外部
- 内层函数(闭包)不提供 setter 或直接赋值能力,只提供基于当前状态计算并返回新对象的方法
- 每次“更新”都显式创建新对象(如用展开运算符、Object.assign 或结构化克隆),旧对象保持 untouched
典型实现示例:带版本控制的不可变配置管理器
下面是一个利用闭包封装内部状态、对外只返回新副本的不可变对象工厂:
function createImmutableConfig(initial = {}) {
// 私有状态,仅闭包内可访问
let state = { ...initial };
return {
// 获取当前快照(返回新对象,不暴露原始引用)
get: () => ({ ...state }),
// “更新”操作:不改原 state,返回新对象 + 新闭包实例
set: (key, value) => createImmutableConfig({
...state,
[key]: value
}),
// 合并多个字段,仍返回全新配置对象
merge: (updates) => createImmutableConfig({
...state,
...updates
})
};
}
// 使用
const config1 = createImmutableConfig({ theme: 'light', lang: 'zh' });
const config2 = config1.set('theme', 'dark'); // 返回新实例
console.log(config1.get()); // { theme: 'light', lang: 'zh' }
console.log(config2.get()); // { theme: 'dark', lang: 'zh' }
console.log(config1 === config2); // false —— 真正不可变语义
与纯对象冻结(Object.freeze)的区别和互补
闭包实现的不可变性是行为层面的不可变,而 Object.freeze 是属性层面的防写入。两者常配合使用:
立即学习“Java免费学习笔记(深入)”;
- 闭包防止外部绕过 API 直接访问和篡改底层数据
- 在 get 方法中返回前,可用 Object.freeze() 对副本做浅冻结,进一步防御意外修改
- 注意:freeze 只是浅冻结,深层嵌套仍可变;闭包则可通过递归克隆或代理(Proxy)强化深度不可变保障
实际开发中的注意事项
这种模式适合小到中等复杂度的状态封装(如表单配置、主题设置、请求参数模板),但需注意:
- 频繁调用 set/merge 会持续生成新对象,若状态较大,要考虑内存开销
- 闭包不解决深拷贝性能问题,必要时应结合 structuredClone、immer 或不可变库(如 Immutable.js)优化
- 调试时无法直接 inspect 内部 state,需依赖 get 接口或增加 debug 方法(仅开发环境启用)


















