闭包是ES5及更早环境中模拟私有成员最可靠的方式,通过函数作用域隐藏数据并仅暴露可控接口;与构造函数不同,闭包中的变量无法被外部访问或枚举。

JavaScript 本身没有原生的私有字段(直到 class #private 语法在 ES2022 正式落地),但在 ES5 及更早环境中,闭包是最可靠、最广泛使用的模拟“私有成员”的手段。它不靠语法糖,而是利用函数作用域和词法环境的天然特性,把数据藏在外部函数内部,只通过返回的函数暴露可控的访问接口。
用闭包创建带私有状态的对象
核心思路是:把想隐藏的数据声明在外部函数作用域中,只让内部返回的方法能访问它;外部无法直接触碰该变量。
- 定义一个工厂函数(如
createUser),在其内部声明变量(如name、_password) - 返回一个对象,其方法(
getName、changePassword)都是在该作用域内定义的,因此能读写这些变量 - 外部只能调用这些方法,无法访问或修改
_password本身
示例:
function createUser(name, password) {let _name = name;
let _password = password;
return {
getName() { return _name; },
setName(newName) { _name = newName; },
authenticate(input) { return input === _password; },
// 不提供直接读取 _password 的方法
};
}
const user = createUser("Alice", "s3cr3t");
console.log(user.getName()); // "Alice"
console.log(user._password); // undefined —— 外部不可见
console.log(user.authenticate("s3cr3t")); // true
与构造函数 + this 的关键区别
传统构造函数(function User() { this.name = ... })中所有属性默认公开,哪怕加下划线前缀(this._password)也只是约定,并非真正受保护。而闭包封装后:
立即学习“Java免费学习笔记(深入)”;
-
_password存在于闭包的私有词法环境中,连user对象本身都“看不到”它 - 没有反射手段(如
Object.keys、for...in)能枚举到它 - 即使调试器能看到闭包变量,运行时代码也无法绕过接口直接操作
结合原型链提升复用性(进阶写法)
纯闭包每次实例化都会重复创建函数,若方法较多,可将公共方法挂到原型上,仅把私有数据保留在闭包中:
function createPrivateData(initialValue) {let value = initialValue;
return { get: () => value, set: (v) => { value = v; } };
}
function Counter(initial) {
const priv = createPrivateData(initial);
this.increment = () => priv.set(priv.get() + 1);
this.value = () => priv.get();
}
// 方法体复用,私有数据隔离
const c1 = new Counter(0);
const c2 = new Counter(100);
c1.increment();
console.log(c1.value(), c2.value()); // 1, 100
注意原型赋值覆盖陷阱
如果使用 Constructor.prototype = {...} 重写整个原型对象,会丢失之前添加的 prototype.xxx 方法。例如:
MyClass.prototype = { b() {} }; // ❌ 覆盖了 a
应统一风格:要么全用对象字面量一次性定义,要么全用点号逐个添加,避免混用导致方法丢失。


















