私有属性模式(Private Property)的实现方案
私有属性模式(Private Property)的实现方案
私有属性模式是一种限制对象属性直接访问的设计模式,它通过特定技术手段确保某些属性仅在对象内部可见。JavaScript 语言本身没有原生私有属性支持,但开发者可以通过多种方式模拟实现这种特性。
命名约定模拟私有属性
最常见的模拟方式是通过命名约定区分私有属性。通常以下划线开头表示该属性为私有:
class User {
constructor(name) {
this._name = name; // 约定为私有属性
}
getName() {
return this._name;
}
}
const user = new User('John');
console.log(user._name); // 仍然可以访问,但不符合约定
这种方案仅通过编程规范约束,实际上属性仍然可被外部访问。它的优势在于实现简单,适合团队协作时作为代码规范使用。
闭包实现真正私有
利用闭包特性可以创建真正的私有变量:
function Car() {
// 真正的私有变量
let speed = 0;
this.accelerate = function() {
speed += 10;
console.log(`当前速度: ${speed}km/h`);
};
this.getSpeed = function() {
return speed;
};
}
const myCar = new Car();
myCar.accelerate(); // 当前速度: 10km/h
console.log(myCar.speed); // undefined
这种方式的缺点是每个实例都会创建方法副本,内存效率较低。适合私有变量较少且实例不多的场景。
WeakMap 存储私有数据
ES6 引入的 WeakMap 可以更优雅地实现私有属性:
const privateData = new WeakMap();
class BankAccount {
constructor(balance) {
privateData.set(this, { balance });
}
deposit(amount) {
const data = privateData.get(this);
data.balance += amount;
}
getBalance() {
return privateData.get(this).balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.balance); // undefined
WeakMap 以实例为键存储私有数据,外部无法直接访问。这种方式不会影响垃圾回收,适合大型应用。
Symbol 作为属性键
ES6 Symbol 也可用于创建伪私有属性:
const _radius = Symbol('radius');
class Circle {
constructor(radius) {
this[_radius] = radius;
}
getRadius() {
return this[_radius];
}
}
const circle = new Circle(5);
console.log(circle.getRadius()); // 5
console.log(circle[_radius]); // 仍可访问,需持有Symbol引用
虽然比命名约定更安全,但通过 Object.getOwnPropertySymbols() 仍可获取Symbol属性。
ES2019 私有字段
最新规范终于引入了真正的私有字段语法:
class Person {
#age; // 私有字段声明
constructor(name, age) {
this.name = name;
this.#age = age;
}
#privateMethod() {
return `年龄: ${this.#age}`;
}
getInfo() {
return `${this.name}, ${this.#privateMethod()}`;
}
}
const person = new Person('Alice', 30);
console.log(person.getInfo()); // Alice, 年龄: 30
console.log(person.#age); // SyntaxError
私有字段以#前缀标识,是语言层面的真正私有实现。目前需要现代浏览器或编译工具支持。
模块模式实现私有
利用IIFE和闭包创建私有空间:
const counter = (function() {
let count = 0;
return {
increment() {
count++;
},
get() {
return count;
}
};
})();
counter.increment();
console.log(counter.get()); // 1
console.log(counter.count); // undefined
这种模式适合创建单例对象,私有变量存在于闭包中,完全无法从外部访问。
Proxy 控制属性访问
使用Proxy代理可以精细控制属性访问:
const createPrivateObject = (obj, privateProps) => {
return new Proxy(obj, {
get(target, prop) {
if (privateProps.includes(prop)) {
throw new Error(`无法访问私有属性 ${prop}`);
}
return target[prop];
},
set(target, prop, value) {
if (privateProps.includes(prop)) {
throw new Error(`无法设置私有属性 ${prop}`);
}
target[prop] = value;
return true;
}
});
};
const user = createPrivateObject({
name: 'Bob',
_password: '123456'
}, ['_password']);
console.log(user.name); // Bob
console.log(user._password); // Error
Proxy方案灵活但性能开销较大,适合需要动态权限控制的场景。
各类方案对比分析
命名约定:简单但无强制约束 闭包:真正私有但内存效率低 WeakMap:平衡性好但语法稍复杂 Symbol:比命名约定更安全 私有字段:未来标准方案 模块模式:适合单例场景 Proxy:最灵活但性能最差
选择方案时应考虑:
- 是否需要真正私有
- 性能要求
- 代码可维护性
- 运行环境支持度
实际应用场景示例
1. 游戏角色状态管理
class Character {
#health = 100;
#inventory = new Set();
takeDamage(amount) {
this.#health = Math.max(0, this.#health - amount);
if (this.#health === 0) {
this.#die();
}
}
#die() {
console.log('角色死亡');
}
addItem(item) {
this.#inventory.add(item);
}
}
2. 表单验证器
const createValidator = () => {
const rules = new Map();
return {
addRule(field, ruleFn) {
if (!rules.has(field)) {
rules.set(field, []);
}
rules.get(field).push(ruleFn);
},
validate(formData) {
return Object.entries(formData).every(([field, value]) => {
const fieldRules = rules.get(field) || [];
return fieldRules.every(rule => rule(value));
});
}
};
};
3. 缓存系统实现
class DataCache {
#cache = new Map();
#maxSize = 100;
get(key) {
return this.#cache.get(key);
}
set(key, value) {
if (this.#cache.size >= this.#maxSize) {
const firstKey = this.#cache.keys().next().value;
this.#cache.delete(firstKey);
}
this.#cache.set(key, value);
}
clear() {
this.#cache.clear();
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn