解释 ES2022 中的 Class Fields (公有/私有实例字段) 和 Private Methods (私有方法/访问器) 如何改变 JavaScript 的面向对象编程模式。

各位代码界的英雄好汉们,欢迎来到今天的 ES2022 新特性分享大会!我是今天的讲师,咱们今天就来聊聊 ES2022 中那些让人又爱又恨,又让人兴奋的 Class Fields 和 Private Methods。

过去,我们在 JavaScript 里搞面向对象编程,总觉得有点…嗯…不那么“正宗”。 感觉像是在玩一个“伪面向对象”的游戏。为什么这么说呢?因为 JavaScript 在 ES2022 之前,并没有真正意义上的私有属性和私有方法。所有的东西,只要你想访问,基本上都能访问到。这就好比你家的门锁是用纸糊的,谁都能随便进出,安全感顿时下降了几个档次。

但是!ES2022 带来了救星!它引入了 Class Fields(公有/私有实例字段)和 Private Methods(私有方法/访问器)。 让我们终于可以在 JavaScript 里实现真正意义上的封装,让我们的代码更加安全、可维护。

一、Class Fields:字段的春天

在 ES2022 之前,我们在类里面定义字段,通常是在构造函数 constructor 里面:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const john = new Person("John", 30);
john.greet(); // 输出: Hello, my name is John and I'm 30 years old.

这种方式没什么大问题,但是总感觉有点啰嗦。而且,如果我想定义一个默认值,还得在构造函数里写,代码多了就显得不够简洁。

ES2022 允许我们在类里面直接声明字段,就像这样:

class Person {
  name = "Unknown"; // 默认值
  age = 0;

  constructor(name, age) {
    this.name = name; //重新赋值,可省略
    this.age = age; //重新赋值,可省略
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const jane = new Person("Jane", 25);
jane.greet(); // 输出: Hello, my name is Jane and I'm 25 years old.

const unknownPerson = new Person();
unknownPerson.greet(); // 输出: Hello, my name is Unknown and I'm 0 years old.

console.log(jane.name) // 输出 Jane

这样看起来是不是清爽多了?而且,可以在声明的时候直接给字段赋默认值,省去了在构造函数里赋值的麻烦。

二、Private Fields:坚固的堡垒

接下来,重头戏来了! Private Fields! 也就是所谓的私有字段。 在 ES2022 之前,我们只能通过命名约定(比如在字段名前面加一个下划线 _ )来表示一个字段是私有的。但这仅仅是一种约定,并不能真正阻止外部访问。

class BankAccount {
  constructor(balance) {
    this._balance = balance; // 用下划线表示私有字段
  }

  getBalance() {
    return this._balance;
  }
}

const account = new BankAccount(1000);
console.log(account.getBalance()); // 输出: 1000
console.log(account._balance); // 输出: 1000  (仍然可以访问!)

你看,就算你用下划线声明了,别人还是可以轻松地访问到你的私有字段,简直形同虚设!

ES2022 使用 # 符号来声明私有字段。 只有在类的内部才能访问这些字段。

class BankAccount {
  #balance = 0; // 使用 # 声明私有字段

  constructor(initialBalance) {
    this.#balance = initialBalance;
  }

  deposit(amount) {
    this.#balance += amount;
  }

  withdraw(amount) {
    if (amount <= this.#balance) {
      this.#balance -= amount;
    } else {
      console.log("Insufficient balance.");
    }
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // 输出: 1300
// console.log(account.#balance); // 报错: Private field '#balance' must be declared in an enclosing class

如果尝试在类的外部访问私有字段,就会报错。 这就真正实现了数据的封装,提高了代码的安全性。

注意点:

  • 私有字段必须在使用之前声明。
  • 私有字段只能在类的内部访问。
  • 每个类的私有字段名称都是唯一的,即使子类和父类有相同的私有字段名称,它们也是不同的。

三、Private Methods and Accessors:私有的力量

除了私有字段,ES2022 还引入了私有方法和私有访问器(getter 和 setter)。 同样使用 # 符号来声明。

1. 私有方法

私有方法只能在类的内部调用,用于实现一些内部逻辑,对外隐藏实现细节。

class Counter {
  #count = 0;

  increment() {
    this.#incrementInternal();
  }

  decrement() {
    this.#decrementInternal();
  }

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

  #incrementInternal() { // 私有方法
    this.#count++;
  }

  #decrementInternal() { // 私有方法
    this.#count--;
  }
}

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

2. 私有访问器 (getter 和 setter)

私有访问器允许我们控制对私有字段的访问和修改,同时隐藏内部实现。

class Temperature {
  #celsius = 0;

  get temperature() { // 公有getter
    return this.#getCelsius();
  }

  set temperature(value) { // 公有setter
    this.#setCelsius(value);
  }

  #getCelsius() { // 私有 getter
    return this.#celsius;
  }

  #setCelsius(value) { // 私有 setter
    if (value < -273.15) {
      console.log("Temperature cannot be below absolute zero.");
      return;
    }
    this.#celsius = value;
  }
}

const temp = new Temperature();
temp.temperature = 25; // 使用 setter
console.log(temp.temperature); // 使用 getter, 输出: 25

temp.temperature = -300; // 使用 setter,触发验证
console.log(temp.temperature); // 使用 getter, 输出: 25  (因为设置失败,值没有改变)

在这个例子中,#getCelsius#setCelsius 是私有的 getter 和 setter。 我们只能通过公有的 temperature 属性来访问和修改 #celsius 字段。 这样就可以在 setCelsius 方法中添加验证逻辑,确保温度值是合法的。

四、Class Fields 和 Private Methods 的优势

特性 优势
Class Fields 简化了字段的声明方式,可以在类里面直接声明字段并赋默认值,代码更简洁。
Private Fields 真正实现了数据的封装,防止外部直接访问和修改私有字段,提高了代码的安全性。
Private Methods 隐藏了内部实现细节,只暴露必要的接口,降低了代码的复杂度,提高了代码的可维护性。
Private Accessors 允许控制对私有字段的访问和修改,可以在 getter 和 setter 中添加验证逻辑,确保数据的有效性。

五、Class Fields 和 Private Methods 的注意事项

  • 浏览器兼容性: 虽然 ES2022 已经发布很久了,但是仍然需要注意浏览器兼容性。 一些老版本的浏览器可能不支持这些新特性。 可以使用 Babel 等工具进行转换,以确保代码在所有浏览器上都能正常运行。
  • 性能: 私有字段的实现方式可能会对性能产生一定的影响。 但是,在大多数情况下,这种影响是可以忽略不计的。 建议在性能敏感的场景下进行测试,以确保性能满足要求。
  • 代码风格: 使用私有字段和私有方法可以提高代码的封装性,但是也可能导致代码的可读性下降。 建议在使用时注意代码风格,添加必要的注释,以提高代码的可理解性。

六、一个更复杂的例子: 模拟一个简单的银行系统

class Bank {
  #accounts = []; // 私有字段,存储所有账户

  createAccount(accountNumber, initialBalance) {
    const newAccount = new BankAccount(accountNumber, initialBalance);
    this.#accounts.push(newAccount);
    return newAccount;
  }

  getAccount(accountNumber) {
    return this.#accounts.find(account => account.accountNumber === accountNumber);
  }

  #getTotalBalance() { // 私有方法,计算所有账户的总余额
    let total = 0;
    for (const account of this.#accounts) {
      total += account.getBalance();
    }
    return total;
  }

  printTotalBalance() {
    console.log(`Total balance of all accounts: ${this.#getTotalBalance()}`);
  }
}

class BankAccount {
  accountNumber;
  #balance = 0; // 私有字段,账户余额

  constructor(accountNumber, initialBalance) {
    this.accountNumber = accountNumber;
    this.#balance = initialBalance;
  }

  deposit(amount) {
    this.#balance += amount;
  }

  withdraw(amount) {
    if (amount <= this.#balance) {
      this.#balance -= amount;
    } else {
      console.log("Insufficient balance.");
    }
  }

  getBalance() {
    return this.#balance;
  }

  // 模拟内部审计流程
  #auditTransaction(transactionType, amount) {
      console.log(`[审计] 账户 ${this.accountNumber} ${transactionType} ${amount}, 当前余额: ${this.#balance}`);
  }

  deposit(amount) {
    this.#balance += amount;
    this.#auditTransaction("存入", amount);
  }

  withdraw(amount) {
    if (amount <= this.#balance) {
      this.#balance -= amount;
      this.#auditTransaction("取出", amount);
    } else {
      console.log("Insufficient balance.");
    }
  }

}

const bank = new Bank();
const account1 = bank.createAccount("123456", 1000);
const account2 = bank.createAccount("789012", 500);

account1.deposit(500);
account2.withdraw(200);

bank.printTotalBalance(); // 输出: Total balance of all accounts: 1800

// 尝试访问私有字段,会报错
// console.log(account1.#balance); // 报错

在这个例子中,Bank 类和 BankAccount 类都使用了私有字段和私有方法。 Bank 类使用 #accounts 存储所有账户,并使用 #getTotalBalance 计算总余额。 BankAccount 类使用 #balance 存储账户余额。 这些私有字段和私有方法都只能在类的内部访问,从而保证了数据的安全性和代码的封装性。 BankAccount 类的 #auditTransaction 方法模拟了一个内部审计流程,只有 depositwithdraw 方法才能调用。

七、总结

ES2022 引入的 Class Fields 和 Private Methods 极大地改变了 JavaScript 的面向对象编程模式。 它们让我们能够编写更加安全、可维护的代码,实现了真正意义上的封装。 虽然在使用过程中需要注意一些细节,但是它们带来的好处是显而易见的。

所以,各位英雄好汉们,赶紧用起来吧! 让我们的 JavaScript 代码更加强大! 让 Bug 无处遁形!

今天的分享就到这里, 感谢大家的聆听! 如果有什么问题,欢迎随时提问。 我们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注