JS `Private Methods` 与 `Private Accessors` (ES2022)：类的封装性增强

admin
Wed, 16 Jul 2025 08:25:48 +0000
Javascript, js, methods, private

大家好，我是今天的主讲人，咱们聊聊 JavaScript 里那些“藏起来的小秘密”——也就是 ES2022 引入的 Private Methods 和 Private Accessors。它们可是提升类封装性的利器！准备好了吗？咱们开始！ 开场白：封装，封装，还是封装！ 在面向对象编程的世界里，封装绝对是核心概念之一。它就像给你的代码穿上盔甲，保护内部数据不被外部随意篡改，让代码更加健壮，更容易维护。 想象一下，你有一辆汽车。你可以开车、加油、换挡，但你不能直接控制发动机内部的每一个零件。那是因为汽车制造商对发动机进行了封装，只暴露了必要的接口给你。 JavaScript 之前的版本，虽然也能模拟私有属性和方法，但总感觉差点意思。ES2022 带来的 Private Methods 和 Private Accessors，才是真正的“原生”私有，让封装变得更加可靠。 第一幕：老朋友的“伪装术”——之前的私有模拟 在 ES2022 之前，JavaScript 社区流行几种模拟私有的方法，但它们都有各自的局限性。 命名约定（约定成俗，但防君子不防小人） 最常见的就是使用下划线 _ 开头来表 …

继续阅读“JS `Private Methods` 与 `Private Accessors` (ES2022)：类的封装性增强”

The post JS `Private Methods` 与 `Private Accessors` (ES2022)：类的封装性增强 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

大家好，我是今天的主讲人，咱们聊聊 JavaScript 里那些“藏起来的小秘密”——也就是 ES2022 引入的 Private Methods 和 Private Accessors。它们可是提升类封装性的利器！准备好了吗？咱们开始！

开场白：封装，封装，还是封装！

在面向对象编程的世界里，封装绝对是核心概念之一。它就像给你的代码穿上盔甲，保护内部数据不被外部随意篡改，让代码更加健壮，更容易维护。

想象一下，你有一辆汽车。你可以开车、加油、换挡，但你不能直接控制发动机内部的每一个零件。那是因为汽车制造商对发动机进行了封装，只暴露了必要的接口给你。

JavaScript 之前的版本，虽然也能模拟私有属性和方法，但总感觉差点意思。ES2022 带来的 Private Methods 和 Private Accessors，才是真正的“原生”私有，让封装变得更加可靠。

第一幕：老朋友的“伪装术”——之前的私有模拟

在 ES2022 之前，JavaScript 社区流行几种模拟私有的方法，但它们都有各自的局限性。

命名约定（约定成俗，但防君子不防小人）

最常见的就是使用下划线 _ 开头来表示私有属性或方法。

class Counter {
  constructor() {
    this._count = 0; // 下划线表示私有，但外部仍然可以访问
  }

  _increment() { // 下划线表示私有，但外部仍然可以调用
    this._count++;
  }

  getCount() {
    return this._count;
  }
}

const counter = new Counter();
counter._count = 100; // 仍然可以修改！
console.log(counter.getCount()); // 输出 100

这种方式完全依赖开发者的自觉性，只能算是一种“君子协定”。只要你想，随时都可以访问和修改。

闭包（稍微靠谱，但略显笨重）

利用闭包的特性，将私有变量和方法放在函数内部，然后返回一个包含公有方法的对象。

function createCounter() {
  let count = 0; // 私有变量

  return {
    increment() {
      count++;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
counter.count = 100; // 无法修改私有变量 count
counter.increment();
console.log(counter.getCount()); // 输出 1

这种方式确实可以实现私有，但代码结构会变得复杂，而且每次创建实例都要创建一个新的闭包，略显笨重。

WeakMap（性能尚可，但语法稍显繁琐）

使用 WeakMap 来存储私有变量，将实例作为键，私有变量作为值。

const _count = new WeakMap();

class Counter {
  constructor() {
    _count.set(this, 0);
  }

  increment() {
    _count.set(this, _count.get(this) + 1);
  }

  getCount() {
    return _count.get(this);
  }
}

const counter = new Counter();
// 无法直接访问私有变量
counter.increment();
console.log(counter.getCount()); // 输出 1

WeakMap 相对闭包性能更好，但语法也比较繁琐，需要单独创建一个 WeakMap 对象，并在每次访问私有变量时都要使用 get 和 set 方法。

第二幕：ES2022 的“真·私有”登场！

ES2022 引入了使用 # 前缀来声明私有属性和方法。这是一种真正的、原生的私有，JavaScript 引擎会强制限制外部访问。

Private Fields（私有字段）

class Counter {
  #count = 0; // 私有字段

  increment() {
    this.#count++;
  }

  getCount() {
    return this.#count;
  }
}

const counter = new Counter();
//console.log(counter.#count); // 报错：Private field '#count' must be declared in an enclosing class
counter.increment();
console.log(counter.getCount()); // 输出 1

class SubCounter extends Counter {
  // incrementSub() {
  //   this.#count++; // 报错：Private field '#count' must be declared in an enclosing class
  // }
}

注意几点：

1. 私有字段必须在类中声明，不能在构造函数外部动态添加。

2. 只能在类内部访问私有字段，包括方法和访问器。

3. 子类无法访问父类的私有字段。这是为了保证封装的彻底性。

Private Methods（私有方法）

class Calculator {
  #add(x, y) { // 私有方法
    return x + y;
  }

  calculate(x, y) {
    return this.#add(x, y);
  }
}

const calculator = new Calculator();
//console.log(calculator.#add(1, 2)); // 报错：Private field '#add' must be declared in an enclosing class
console.log(calculator.calculate(1, 2)); // 输出 3

私有方法只能在类内部调用，外部无法访问。

Private Accessors（私有访问器）

私有访问器允许你控制对私有字段的读取和设置，就像 getter 和 setter 一样，只不过是私有的。

class Temperature {
  #celsius = 0;

  get #fahrenheit() { // 私有 getter
    return (this.#celsius * 9) / 5 + 32;
  }

  set #fahrenheit(value) { // 私有 setter
    this.#celsius = ((value - 32) * 5) / 9;
  }

  getCelsius() {
    return this.#celsius;
  }

  setFahrenheit(value) {
    this.#fahrenheit = value; // 通过私有 setter 设置摄氏度
  }

  getFahrenheit() {
    return this.#fahrenheit; // 通过私有 getter 获取华氏度
  }
}

const temp = new Temperature();
temp.setFahrenheit(68);
console.log(temp.getCelsius()); // 输出 20
console.log(temp.getFahrenheit()); // 输出 68

私有访问器可以让你在读取和设置私有字段时执行一些额外的逻辑，比如验证输入、触发事件等等。

第三幕：in 操作符的妙用

in 操作符可以用来检查一个对象是否拥有某个属性。对于私有字段，in 操作符只能在类内部使用。

class MyClass {
  #privateField;

  hasPrivateField() {
    return #privateField in this; // 返回 true
  }

  static hasPrivateField(obj) {
    return #privateField in obj; // 报错：Private field '#privateField' must be declared in an enclosing class
  }
}

const myObj = new MyClass();
console.log(myObj.hasPrivateField()); // 输出 true
//console.log(MyClass.hasPrivateField(myObj)); // 报错

in 操作符对于检查私有字段的存在性非常有用，特别是在处理复杂的逻辑时。

第四幕：为什么要用“真·私有”？

更强的封装性： 真正的私有，防止外部随意访问和修改内部数据，保证类的完整性和一致性。

更好的代码可维护性： 修改内部实现不会影响外部代码，降低了重构的风险。

更安全的代码： 避免恶意代码通过访问私有属性来破坏程序的行为。

避免命名冲突： 不再需要担心私有属性的命名与其他属性冲突。

第五幕：表格对比：新旧私有方案大PK

特性 | 命名约定（_） | 闭包 | WeakMap | Private Fields/Methods/Accessors（#）
私有性 | 弱 | 强 | 较强 | 极强
易用性 | 简单 | 一般 | 一般 | 简单
性能 | 高 | 较低 | 较高 | 高
代码复杂度 | 低 | 较高 | 一般 | 低
是否原生支持 | 否 | 否 | 否 | 是
子类访问父类私有 | 可以 | 不可以 | 不可以 | 不可以
适用场景 | 小型项目，不强调安全性 | 需要强私有性 | 需要一定性能 | 大型项目，强调安全性，需要原生支持

第六幕：一些注意事项

浏览器兼容性： ES2022 的特性需要较新的浏览器版本支持。在使用前请确保目标环境支持。

TypeScript 的配合： TypeScript 也支持私有属性和方法，并且提供了更丰富的类型检查功能。

谨慎使用私有： 过度使用私有可能会降低代码的灵活性。应该根据实际情况，选择合适的封装级别。

第七幕：案例分析：一个简单的银行账户

让我们用一个银行账户的例子来演示如何使用 Private Methods 和 Private Accessors。

class BankAccount {
  #balance = 0; // 私有余额

  constructor(initialBalance) {
    if (initialBalance > 0) {
      this.#balance = initialBalance;
    } else {
      console.warn("Initial balance must be positive.");
    }
  }

  #validateAmount(amount) { // 私有方法，验证金额
    if (typeof amount !== 'number' || amount <= 0) {
      console.error("Invalid amount.");
      return false;
    }
    return true;
  }

  deposit(amount) {
    if (this.#validateAmount(amount)) {
      this.#balance += amount;
      console.log(`Deposited ${amount}. New balance: ${this.getBalance()}`);
    }
  }

  withdraw(amount) {
    if (this.#validateAmount(amount)) {
      if (amount <= this.#balance) {
        this.#balance -= amount;
        console.log(`Withdrew ${amount}. New balance: ${this.getBalance()}`);
      } else {
        console.error("Insufficient funds.");
      }
    }
  }

  getBalance() {
    return this.#balance;
  }

  // 私有 accessor ，隐藏内部计息逻辑
  get #interestRate() {
    return 0.01; // 假设利率为 1%
  }

  calculateInterest() {
    return this.#balance * this.#interestRate;
  }

  // 试图直接访问私有属性
  // externalAccess() {
  //  console.log(this.#interestRate);  //报错
  // }
}

const account = new BankAccount(100);
account.deposit(50);
account.withdraw(20);
console.log(`Current balance: ${account.getBalance()}`);
console.log(`Interest earned: ${account.calculateInterest()}`);

//account.#balance = 1000; // 报错：无法访问私有属性

在这个例子中，#balance 是私有字段，#validateAmount 是私有方法，#interestRate是私有accessor。它们只能在 BankAccount 类内部访问，保证了账户余额的安全性，以及验证金额的逻辑不被外部篡改。

总结：拥抱“真·私有”，编写更健壮的代码

Private Methods 和 Private Accessors 是 JavaScript 封装能力的重要补充。它们提供了一种更可靠、更原生的方式来隐藏类的内部实现细节，编写出更加健壮、易于维护的代码。赶紧用起来，让你的代码更上一层楼吧！

希望今天的分享对大家有所帮助！下次再见！