闭包是对象字面量实现私有成员的可靠方案:通过IIFE将辅助变量和函数封装在函数作用域内,仅暴露必要方法;支持多实例隔离,兼容旧环境,且比#私有字段更适用于字面量场景。

对象字面量本身不支持私有成员,但通过闭包可以绕过这个限制,在外部无法直接访问辅助逻辑的前提下,只暴露必要的接口。
用立即执行函数包裹字面量
把对象创建过程放进一个自调用函数里,让辅助变量和函数落在函数作用域内,不被外部触及。
- 辅助逻辑(如校验、计算、缓存)定义在函数内部,不是对象的属性
- 返回的对象只包含需要调用的方法,这些方法能访问闭包里的变量和函数
- 每次调用都生成独立作用域,多个实例互不影响
例如:
const api = (function() {
const cache = new Map();
function isValid(url) { return url && typeof url === 'string' && url.startsWith('https://'); }
return {
fetchData(url) {
if (!isValid(url)) throw new Error('Invalid URL');
if (cache.has(url)) return cache.get(url);
const data = { id: Date.now(), url };
cache.set(url, data);
return data;
},
};
})();
这里 cache 和 isValid 完全隐藏,外部只能调用 fetchData。
避免意外暴露私有逻辑
对象字面量容易让人误以为所有内容都可访问,实际要警惕几种常见泄露方式:
- 不要把辅助函数赋值给对象属性(如
helper: function() {...}),它就变成公开方法了 - 不要在返回对象中引用闭包外的变量(比如全局或上级作用域的变量),否则私有性失效
- 如果返回的是普通对象而非函数,确保没有通过
this或原型链间接暴露内部状态
需要多实例时改用工厂函数
如果业务需要多个隔离的“私有环境”,别反复复制 IIFE,而是封装成可复用的工厂:
- 每个调用生成新的闭包,各自拥有独立的私有变量
- 适合配置化场景,比如不同验证规则、不同缓存策略的 API 实例
- 比单个 IIFE 更易测试和扩展
例如:
function createApi(options = {}) {
const { timeout = 5000, retry = 2 } = options;
const pending = new Set();
return {
request(url) {
if (pending.has(url)) return Promise.reject('Already pending');
pending.add(url);
return fetch(url, { signal: AbortSignal.timeout(timeout) })
.finally(() => pending.delete(url));
},
};
}
这样 pending 和 timeout 对每个实例都是私有的。
现代写法的兼容性提醒
ES2022 的 # 私有字段只适用于 class,不能直接用于对象字面量。所以闭包仍是字面量场景下最可靠的选择:
- 想用
#语法?得改写成 class,再实例化 - 需要兼容旧浏览器或 Node.js 低版本?闭包是唯一稳定方案
- WeakMap 方案虽能配合原型复用方法,但对象字面量天然无原型继承链,不适合直接套用
不复杂但容易忽略

















