在JavaScript中,类的封装性一直是开发者关注的重点,早期版本没有提供原生的私有成员支持,因此需要通过各种技巧模拟类的私有方法和属性,避免外部直接访问或修改内部状态。这些模拟方案各有特点,适用于不同的开发场景。

使用闭包模拟私有成员
闭包是JavaScript中模拟私有成员最经典的方式,利用函数作用域的特性,将私有变量和函数定义在构造函数内部,外部无法直接访问。
function Person(name) {
// 私有属性
let _name = name;
// 私有方法
function _formatName() {
return `用户:${_name}`;
}
// 公共方法,可以访问私有成员
this.getName = function() {
return _formatName();
};
this.setName = function(newName) {
_name = newName;
};
}
const p = new Person('张三');
console.log(p.getName()); // 输出 用户:张三
p.setName('李四');
console.log(p.getName()); // 输出 用户:李四
// 无法直接访问私有属性_name和私有方法_formatName
console.log(p._name); // 输出 undefined
这种方式的缺点是每个实例都会创建一份私有方法和属性的副本,会增加内存开销,适合私有成员较少的场景。
使用Symbol模拟私有成员
Symbol是ES6引入的唯一值类型,用Symbol作为属性键,外部很难意外访问到该属性,不过仍可以通过Object.getOwnPropertySymbols获取到。
const _name = Symbol('name');
const _formatName = Symbol('formatName');
class Person {
constructor(name) {
// 私有属性
this[_name] = name;
}
// 私有方法
[_formatName]() {
return `用户:${this[_name]}`;
}
getName() {
return this[_formatName]();
}
setName(newName) {
this[_name] = newName;
}
}
const p = new Person('张三');
console.log(p.getName()); // 输出 用户:张三
// 无法通过常规方式访问Symbol属性
console.log(p._name); // 输出 undefined
// 但可以通过getOwnPropertySymbols获取
const symbols = Object.getOwnPropertySymbols(p);
console.log(p[symbols[0]]); // 输出 张三
使用WeakMap模拟私有成员
WeakMap的键是弱引用,不会阻止垃圾回收,适合用来存储实例对应的私有数据,避免内存泄漏问题。
const privateData = new WeakMap();
class Person {
constructor(name) {
// 存储私有属性
privateData.set(this, {
name: name
});
}
// 私有方法
_formatName() {
const data = privateData.get(this);
return `用户:${data.name}`;
}
getName() {
return this._formatName();
}
setName(newName) {
const data = privateData.get(this);
data.name = newName;
}
}
const p = new Person('张三');
console.log(p.getName()); // 输出 用户:张三
// 无法直接访问privateData中的私有数据
console.log(privateData.get(p)); // 外部可以获取到,但可以通过封装进一步隐藏
可以将WeakMap定义在模块内部,不对外暴露,这样外部就无法访问到私有数据了。
ES2022原生私有字段和方法
ES2022正式引入了原生的私有字段和私有方法语法,使用#作为前缀,原生支持封装,外部完全无法访问。
class Person {
// 私有属性
#name;
constructor(name) {
this.#name = name;
}
// 私有方法
#formatName() {
return `用户:${this.#name}`;
}
getName() {
return this.#formatName();
}
setName(newName) {
this.#name = newName;
}
}
const p = new Person('张三');
console.log(p.getName()); // 输出 用户:张三
// 访问私有成员会直接报错
try {
console.log(p.#name);
} catch(e) {
console.log('访问私有属性报错:', e.message);
}
try {
p.#formatName();
} catch(e) {
console.log('访问私有方法报错:', e.message);
}
原生私有成员是目前最优的实现方案,语法简洁,封装性最强,只要运行环境支持ES2022就可以优先使用。
不同方案对比
| 方案 | 封装性 | 内存开销 | 兼容性 |
|---|---|---|---|
| 闭包 | 高 | 较高 | 所有环境 |
| Symbol | 中 | 低 | ES6及以上 |
| WeakMap | 高 | 低 | ES6及以上 |
| 原生私有字段 | 最高 | 低 | ES2022及以上 |
开发者可以根据项目的兼容性要求和功能需求选择合适的私有成员模拟方案,在支持新语法的环境中优先使用原生私有字段,获得更好的开发体验和封装效果。
JavaScript私有属性私有方法类模拟闭包修改时间:2026-07-09 08:42:31