logo
火山博客
导航

JavaScript 私有属性详解

2025-01-26
14阅读时间6分钟

什么是私有属性?

私有属性是面向对象编程中一个重要的概念,它允许我们将某些属性或方法限制在类的内部使用,不允许外部直接访问。这有助于实现封装,提高代码的安全性和可维护性。

JavaScript 中实现私有属性的几种方式

1. 闭包实现私有属性(ES5)

Javascript
function Person() {
  // 私有属性
  let privateAge = 0;
  
  // 特权方法
  this.getAge = function() {
    return privateAge;
  }
  
  this.setAge = function(age) {
    if(age > 0) {
      privateAge = age;
    }
  }
}

const person = new Person();
person.setAge(25);
console.log(person.getAge()); // 25
console.log(person.privateAge); // undefined

2. Symbol 实现私有属性(ES6)

Javascript
const ageSymbol = Symbol('age');

class Person {
  constructor() {
    this[ageSymbol] = 0;
  }
  
  getAge() {
    return this[ageSymbol];
  }
}

const person = new Person();
console.log(person[ageSymbol]); // 0
// Symbol属性虽然可以访问,但不会在常规的属性枚举中出现
console.log(Object.keys(person)); // []

3. 私有字段(#)语法 (ES2022)

Javascript
class Person {
  #age = 0; // 私有字段
  
  constructor() {
    this.#age = 0;
  }
  
  getAge() {
    return this.#age;
  }
  
  setAge(age) {
    if(age > 0) {
      this.#age = age;
    }
  }
}

const person = new Person();
person.setAge(25);
console.log(person.getAge()); // 25
// console.log(person.#age); // SyntaxError

4. WeakMap 实现私有属性(ES6)

Javascript
const privateProps = new WeakMap();

class Person {
  constructor() {
    privateProps.set(this, {
      age: 0,
      name: ''
    });
  }
  
  getAge() {
    return privateProps.get(this).age;
  }
  
  setAge(age) {
    if(age > 0) {
      privateProps.get(this).age = age;
    }
  }
}

const person = new Person();
person.setAge(25);
console.log(person.getAge()); // 25
console.log(privateProps.get(person)); // { age: 25, name: '' }

WeakMap 实现的优点:

  • 真正的私有性,外部无法轻易访问
  • 不会造成内存泄漏,当实例被垃圾回收时,WeakMap 中的私有数据也会被回收
  • 可以存储多个私有属性

5. 私有方法的实现

私有方法与私有属性的实现方式类似,以下展示几种常用方式:

Javascript
// 使用私有字段语法
class Example {
  #privateMethod() {
    return 'private method called';
  }
  
  publicMethod() {
    return this.#privateMethod();
  }
}

// 使用 WeakMap 实现
const privateMethods = new WeakMap();

class Example2 {
  constructor() {
    privateMethods.set(this, {
      privateMethod: () => {
        return 'private method called';
      }
    });
  }
  
  publicMethod() {
    return privateMethods.get(this).privateMethod();
  }
}

6. TypeScript 中的私有属性

TypeScript 提供了额外的访问修饰符来控制属性的可访问性:

Typescript
class Person {
  private age: number;  // TypeScript 的 private 修饰符
  #secretAge: number;   // ECMAScript 的私有字段
  
  constructor() {
    this.age = 0;
    this.#secretAge = 0;
  }
  
  // protected 修饰符允许在子类中访问
  protected getSecretAge() {
    return this.#secretAge;
  }
}

class Employee extends Person {
  getAge() {
    // this.age;       // 错误: age 是私有的
    return this.getSecretAge(); // 正确: protected 方法可以在子类中访问
  }
}

TypeScript 私有属性的特点:

  • private 修饰符只在编译时检查,运行时仍可访问
  • 可以与 ECMAScript 的私有字段(#)结合使用
  • 提供了 protected 修饰符用于继承场景
  • 在 IDE 中有更好的类型提示和错误检查

各种实现方式的对比

  1. 闭包方式

    • 优点: 兼容性好,ES5就能使用
    • 缺点: 每个实例都会创建新的方法副本,占用更多内存
  2. Symbol方式

    • 优点: 属性不会在常规枚举中出现
    • 缺点: 实际上属性还是可以被访问到,只是增加了访问难度
  3. 私有字段语法

    • 优点:
      • 语法清晰直观
      • 真正的私有属性,外部无法访问
      • 在类定义时就声明私有字段
    • 缺点:
      • 兼容性要求较高,需要较新的浏览器支持

最佳实践建议

  1. 如果你的项目需要支持旧版浏览器,可以使用闭包方式

  2. 如果你的项目使用现代构建工具且目标浏览器支持,强烈推荐使用私有字段(#)语法:

    • 语法更清晰
    • 性能更好
    • 真正的私有性保证
  3. 合理使用私有属性:

    • 将实现细节隐藏在私有属性中
    • 通过公共方法提供受控的访问方式
    • 避免过度使用私有属性,保持代码的可维护性

使用私有属性的实际场景

Javascript
class UserAccount {
  #balance = 0;
  #transactions = [];
  
  deposit(amount) {
    if (typeof amount !== 'number' || isNaN(amount)) {
      throw new Error('存款金额必须是有效数字');
    }
    if (amount <= 0) {
      throw new Error('存款金额必须大于0');
    }
    this.#balance += amount;
    this.#addTransaction('deposit', amount);
  }
  
  withdraw(amount) {
    if (typeof amount !== 'number' || isNaN(amount)) {
      throw new Error('取款金额必须是有效数字');
    }
    if (amount <= 0) {
      throw new Error('取款金额必须大于0');
    }
    if (amount > this.#balance) {
      throw new Error('余额不足');
    }
    this.#balance -= amount;
    this.#addTransaction('withdraw', amount);
    return true;
  }
  
  #addTransaction(type, amount) {
    this.#transactions.push({
      type,
      amount,
      date: new Date()
    });
  }
  
  getBalance() {
    return this.#balance;
  }
  
  getTransactionHistory() {
    return [...this.#transactions];
  }
}

这个例子展示了私有属性在实际应用中的使用:

  • #balance 和 #transactions 被封装在类内部
  • 只能通过公共方法进行操作
  • 保证了数据的完整性和安全性

总结

私有属性是实现封装的重要机制,JavaScript 提供了多种实现方式。现代JavaScript推荐使用私有字段语法(#),它提供了最好的私有性保证和开发体验。在实际开发中,合理使用私有属性可以提高代码的可维护性和安全性。

2024 © Powered by
hsBlog
|
后台管理