JS `private` 字段 (`#`) 的实际应用：实现严格封装的类

admin
Mon, 21 Jul 2025 08:52:00 +0000
Javascript, js, private, 字段

各位朋友，大家好！今天咱们来聊聊 JavaScript 中 private 字段（#），这玩意儿听起来挺高大上，但实际上用好了，能让你的代码更安全，更可靠，也更容易维护。咱们的目标就是：彻底搞懂它，并且能熟练地运用到实际开发中。 开场白：为什么需要“私有”？ 想象一下，你是一个玩具设计师，设计了一个非常酷炫的遥控车。遥控车里面有很多精密的齿轮、电路板，还有一些非常重要的参数，比如电池电量、电机转速等等。 如果你允许小朋友们随便拆开遥控车，随便调整里面的参数，那会发生什么？ 遥控车坏掉： 小朋友可能会把齿轮搞错位，或者烧坏电路板。 遥控车行为异常： 电池电量被随意修改，可能导致遥控车“假死”；电机转速被调得过高，可能会烧毁电机。 所以，玩具设计师需要一种方法，把遥控车内部的关键部件和参数“藏起来”，只允许通过特定的接口（比如遥控器）来控制遥控车。 在编程世界里，也一样。我们需要一种方法，把类的内部状态和行为“藏起来”，防止外部代码随意修改，从而保证类的稳定性和可靠性。这就是“封装”的思想。 “私有”的历史：JavaScript 的“伪私有”时代 在 private 字段（#）出现之前，J …

继续阅读“JS `private` 字段 (`#`) 的实际应用：实现严格封装的类”

The post JS `private` 字段 (`#`) 的实际应用：实现严格封装的类 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

各位朋友，大家好！今天咱们来聊聊 JavaScript 中 private 字段（#），这玩意儿听起来挺高大上，但实际上用好了，能让你的代码更安全，更可靠，也更容易维护。咱们的目标就是：彻底搞懂它，并且能熟练地运用到实际开发中。

开场白：为什么需要“私有”？

想象一下，你是一个玩具设计师，设计了一个非常酷炫的遥控车。遥控车里面有很多精密的齿轮、电路板，还有一些非常重要的参数，比如电池电量、电机转速等等。

如果你允许小朋友们随便拆开遥控车，随便调整里面的参数，那会发生什么？

遥控车坏掉： 小朋友可能会把齿轮搞错位，或者烧坏电路板。

遥控车行为异常： 电池电量被随意修改，可能导致遥控车“假死”；电机转速被调得过高，可能会烧毁电机。

所以，玩具设计师需要一种方法，把遥控车内部的关键部件和参数“藏起来”，只允许通过特定的接口（比如遥控器）来控制遥控车。

在编程世界里，也一样。我们需要一种方法，把类的内部状态和行为“藏起来”，防止外部代码随意修改，从而保证类的稳定性和可靠性。这就是“封装”的思想。

“私有”的历史：JavaScript 的“伪私有”时代

在 private 字段（#）出现之前，JavaScript 实现“私有”的方式可谓是八仙过海，各显神通。

命名约定（_ 或 __）： 这是一种最简单的“约定式私有”。我们在变量或方法名前面加上 _ 或 __，表示这是私有的，不应该在外部直接访问。

class Car {
  constructor() {
    this._speed = 0; // 用 _speed 表示私有属性
  }

  accelerate() {
    this._speed += 10;
  }

  getSpeed() {
    return this._speed;
  }
}

const myCar = new Car();
myCar._speed = 1000; // 仍然可以访问，只是不推荐
console.log(myCar.getSpeed()); // 输出 1000

问题： 这只是一个“君子协定”，并没有真正的约束力。外部代码仍然可以随意访问和修改 _speed。

闭包（Closure）： 利用闭包的特性，将变量或方法包裹在一个函数内部，外部无法直接访问。

const createCar = () => {
  let speed = 0; // speed 变量被闭包保护

  return {
    accelerate() {
      speed += 10;
    },
    getSpeed() {
      return speed;
    }
  };
};

const myCar = createCar();
// console.log(myCar.speed); // 无法访问 speed
myCar.accelerate();
console.log(myCar.getSpeed()); // 输出 10

问题： 这种方式可以实现真正的私有，但是每个实例都会创建一个新的闭包，导致内存占用增加，并且无法使用 prototype 共享方法。

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

const carSpeeds = new WeakMap();

class Car {
  constructor() {
    carSpeeds.set(this, 0); // 将实例和 speed 关联
  }

  accelerate() {
    carSpeeds.set(this, carSpeeds.get(this) + 10);
  }

  getSpeed() {
    return carSpeeds.get(this);
  }
}

const myCar = new Car();
// console.log(carSpeeds.get(myCar)); // 无法直接访问 WeakMap
myCar.accelerate();
console.log(myCar.getSpeed()); // 输出 10

问题： 这种方式相对比较复杂，代码可读性较差。

private 字段（#）：真正的私有！

终于，JavaScript 引入了 private 字段（#），这是一种真正的、强制的私有机制。

class Car {
  #speed = 0; // 使用 # 定义私有字段

  accelerate() {
    this.#speed += 10;
  }

  getSpeed() {
    return this.#speed;
  }
}

const myCar = new Car();
// console.log(myCar.#speed); // 报错：私有字段 '#speed' 必须在封闭类中声明
myCar.accelerate();
console.log(myCar.getSpeed()); // 输出 10

class ElectricCar extends Car{
    increaseSpeed(){
        //this.#speed += 20; //报错： 私有字段 '#speed' 必须在封闭类中声明
    }
}

关键点：

# 符号： 在字段名前面加上 # 符号，表示这是一个私有字段。

封闭类： 私有字段只能在声明它的类中访问。这意味着子类也无法访问父类的私有字段。

编译时错误： 如果尝试在类外部访问私有字段，或者在子类中访问父类的私有字段，会直接抛出 SyntaxError 错误。

private 字段的优点

真正的私有性： 强制性的私有，杜绝了外部代码随意修改内部状态的可能。

代码清晰： 使用 # 符号，清晰地标识了哪些字段是私有的，提高了代码可读性。

安全性： 防止恶意代码篡改内部状态，提高了代码的安全性。

可维护性： 可以放心地修改类的内部实现，而不用担心影响到外部代码。

private 字段的局限性

子类无法访问父类的私有字段： 这可能会限制代码的复用性。

无法在构造函数之外声明私有字段： 必须在类的主体中声明私有字段。

private 字段的应用场景

封装敏感数据： 例如，密码、密钥等。

保护内部状态： 防止外部代码随意修改类的内部状态，保证类的稳定性和可靠性。

隐藏实现细节： 将类的内部实现细节隐藏起来，只暴露必要的接口给外部使用。

高级用法：private 字段与方法

private 不仅仅可以用于字段，还可以用于方法！

class Counter {
  #count = 0;

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

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

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

const myCounter = new Counter();
myCounter.incrementPublicly();
console.log(myCounter.getCount()); // 输出 1
// myCounter.#increment(); // 报错：私有方法 '#increment' 必须在封闭类中声明

private 字段与静态字段/方法

private 字段也可以与 static 关键字一起使用，创建私有静态字段和方法。

class MyClass {
  static #counter = 0;

  static #increment() {
    MyClass.#counter++;
  }

  static getCounter() {
    MyClass.#increment();
    return MyClass.#counter;
  }
}

console.log(MyClass.getCounter()); // 输出 1
// console.log(MyClass.#counter); // 报错：私有字段 '#counter' 必须在封闭类中声明

private 字段与 getter/setter

虽然不能直接访问 private 字段，但是可以通过 getter 和 setter 间接访问和修改。

class Person {
  #age = 0;

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

  set age(newAge) {
    if (newAge >= 0 && newAge <= 150) {
      this.#age = newAge;
    } else {
      console.warn("Invalid age!");
    }
  }
}

const person = new Person();
person.age = 30;
console.log(person.age); // 输出 30
person.age = -10; // 输出 "Invalid age!"

实际案例：模拟银行账户

让我们用 private 字段来模拟一个银行账户，保护账户余额。

class BankAccount {
  #balance = 0;

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

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    } else {
      console.error("Deposit amount must be positive.");
    }
  }

  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
    } else {
      console.error("Invalid withdrawal amount.");
    }
  }

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

const myAccount = new BankAccount(1000);
myAccount.deposit(500);
myAccount.withdraw(200);
console.log(myAccount.getBalance()); // 输出 1300
// myAccount.#balance = -10000; // 报错：私有字段 '#balance' 必须在封闭类中声明

总结：private 字段，你值得拥有！

private 字段是 JavaScript 中一个非常重要的特性，它提供了真正的、强制的私有机制，可以帮助我们编写更安全、更可靠、更容易维护的代码。虽然它有一些局限性，但是只要合理运用，就能发挥出巨大的作用。

表格：private 字段 vs. 其他“伪私有”方式

特性 | 命名约定（_） | 闭包 | WeakMap | private 字段（#）
私有性 | 约定式 | 真正私有 | 真正私有 | 真正私有
强制性 | 否 | 是 | 是 | 是
性能 | 高 | 较低 | 中等 | 高
可读性 | 较高 | 较低 | 较低 | 较高
内存占用 | 低 | 较高 | 中等 | 低
易用性 | 高 | 中等 | 中等 | 高
子类可访问 | 是 | 否 | 否 | 否

最后，给大家留个思考题：

如果我们需要实现一个可复用的组件，并且希望子类能够访问父类的一些内部状态，但是又不希望外部代码直接访问，应该怎么做？（提示：可以考虑使用 protected 概念，或者使用 Symbol 作为键来存储“受保护”的属性。）

希望今天的讲解对大家有所帮助。谢谢大家！