通过闭包实现的私有变量与Class的私有变量
七崽爱吃小饼干2026/01/20阅读 0专栏 JavaScript
一、Class 私有属性 ≠ 闭包实现
ES6 Class 的私有属性(# 开头的属性)是 JS 语言层面原生支持的特性,并非通过闭包实现;而我们之前用闭包实现的“私有变量”,是开发者利用语言特性(作用域+闭包)手动模拟的封装效果。
两者虽然都能实现“私有性”,但底层原理、表现形式、使用限制完全不同。
二、对比:闭包模拟私有变量 vs Class 原生私有属性
1. 闭包模拟私有变量(手动封装)
这是 ES6 之前没有 Class 私有属性时的“变通方案”,核心是利用函数作用域和闭包,让变量只在函数内部可访问,外部无法直接获取。
javascript
// 闭包模拟私有变量
function Person(name) {
// 闭包私有变量:外部无法直接访问,没有挂载到this实例上
let _age = 18; // 约定用下划线标识私有(仅规范,非语法限制)
this.name = name; // 公有属性
// 闭包方法:能访问 _age
this.getAge = function() {
return _age;
};
this.setAge = function(newAge) {
if (newAge > 0) _age = newAge;
};
}
const p = new Person("张三");
console.log(p.name); // 张三(公有属性可访问)
console.log(p._age); // undefined(闭包变量外部拿不到)
console.log(p.getAge()); // 18(通过方法间接访问)
核心特点:
_age存在于构造函数的作用域中,只有内部方法(getAge/setAge)能访问;- 本质是闭包保留了构造函数作用域的变量,外部无法直接访问;
- 下划线
_age只是开发者的“命名约定”,并非语法层面的私有(如果强行通过 hack 手段,理论上能拿到)。
2. Class 原生私有属性(语言层面封装)
ES2022 引入的 # 开头的私有属性,是 JS 引擎原生支持的特性,和闭包无关。
javascript
// Class 原生私有属性
class Person {
// 私有属性:必须用 # 声明,且只能在类内部访问
#age = 18;
// 公有属性
name;
constructor(name) {
this.name = name;
}
// 类内部方法可访问私有属性
getAge() {
return this.#age;
}
setAge(newAge) {
if (newAge > 0) this.#age = newAge;
}
}
const p = new Person("李四");
console.log(p.name); // 李四(公有属性可访问)
console.log(p.#age); // 报错!SyntaxError: Private field '#age' must be declared in an enclosing class
console.log(p.getAge()); // 18(通过方法间接访问)
核心特点:
#age是语法层面的私有属性,JS 引擎会直接禁止外部访问(哪怕是 hack 手段也无法绕过);- 私有属性属于实例本身(挂载在
this上),而非闭包保留的作用域变量; - 无需依赖函数作用域/闭包,是 Class 语法的原生能力。
三、关键区别总结表
| 特性 | 闭包模拟私有变量 | Class 原生私有属性(#) |
|---|---|---|
| 实现原理 | 依赖函数作用域 + 闭包 | JS 引擎原生支持(语法层面) |
| 外部访问限制 | 软限制(约定俗成,可 hack) | 硬限制(语法禁止,无法访问) |
| 变量归属 | 构造函数作用域(非实例本身) | 类的实例本身(挂载在 this 上) |
| 兼容性 | 所有 JS 环境都支持 | ES2022 及以上环境支持 |
总结
- 核心区别:Class 私有属性(
#)是 JS 语言原生特性,和闭包无关;闭包是手动利用作用域特性模拟的私有性,属于“编程技巧”而非“语法特性”; - 使用场景:现代开发中优先用 Class 原生私有属性(
#)实现封装(更规范、更安全);闭包模拟私有变量多用于兼容旧环境,或非 Class 场景(如函数式编程); - 本质相通点:两者最终都能实现“变量私有化”,但底层原理、安全性、使用方式完全不同。