JavaScript 中无原生私有字段语法(ES2022 前),闭包是最经典可靠的私有变量实现方式:变量封闭于函数作用域,仅通过特权方法间接访问;WeakMap 可结合原型复用方法并隔离私有状态;ES2022 起支持 #语法级私有字段,更简洁高效。

JavaScript 中没有原生的私有字段语法(ES2022 之前),闭包是实现真正私有变量最经典、最可靠的方式——变量被封闭在函数作用域内,外部无法直接访问,只能通过返回的特权方法间接操作。
基本模式:构造函数 + 闭包封装
在构造函数内部声明变量,返回一个对象,其方法引用这些变量。由于词法作用域,这些方法能持续访问构造函数内的局部变量,而外部无法绕过方法直接读写。
- 私有变量定义在函数体内,不是 this 属性,不挂载到实例上
- 所有“公有方法”都定义在闭包内,共享对私有数据的访问权
- 每次调用构造函数都会创建独立的闭包环境,保证实例间私有状态隔离
示例:
function Counter() { let count = 0; // 私有变量,外部不可见 <p>return { increment() {count++;}, decrement() { count--;}, value() { return count;} }; }</p><p>const c1 = Counter(); const c2 = Counter(); c1.increment(); console.log(c1.value()); // 1 console.log(c2.value()); // 0 —— 互不影响 </p>
结合原型与闭包:兼顾复用与私有
纯闭包方式每次实例化都新建方法,内存开销略大。可将公有方法放在原型上,同时用闭包保存私有数据映射表(如 WeakMap),实现方法复用与私有状态分离。
立即学习 “Java 免费学习笔记(深入)”;
- WeakMap 键必须是对象,值可任意,适合绑定实例与私有数据
- 私有数据仅对闭包内方法可见,WeakMap 自身不暴露给外部
- 原型方法通过 WeakMap.get(this) 获取对应私有状态,保持逻辑清晰
示例:
const privateData = new WeakMap(); <p>function Person(name) {privateData.set(this, { name: name || 'anonymous', _age: 0}); }</p><p>Person.prototype.getName = function() { return privateData.get(this).name; };</p><p>Person.prototype.setAge = function(age) {if (age >= 0) {privateData.get(this)._age = age; } };</p><p>Person.prototype.getAge = function() { return privateData.get(this)._age; };</p>
ES6 类 + # 私有字段(现代替代方案)
虽然闭包方案经典可靠,但 ES2022 起已支持真正的语法级私有字段(#name)。它比闭包更简洁、更易读,且被引擎深度优化。
- # 开头的字段和方法只能在类内部访问,运行时强制检查
- 无需手动管理 WeakMap 或闭包,语义明确,调试友好
- 仍建议在需要兼容老环境(如 IE、旧版 Node.js)时回退到闭包或 WeakMap 方案
示例:
class BankAccount {#balance = 0; <p>constructor(initial) {this.#balance = initial || 0;}</p><p>deposit(amount) {if (amount > 0) this.#balance += amount; }</p><p>getBalance() { return this.#balance;} }</p>
注意事项与常见误区
闭包实现私有性虽强大,但需注意边界问题:
- 不要在闭包内返回私有变量的引用(如数组、对象),否则外部仍可修改内容
- 避免在循环中创建闭包并绑定索引(如 for(var i…)),应使用 let 或立即执行函数修正作用域
- 私有变量无法被 JSON.stringify 序列化,也不参与 for…in 遍历,这是设计使然,不是 bug
- 调试时 Chrome DevTools 可显示闭包变量(Closure scope),但无法在控制台直接修改,确保了安全性