JavaScript内核与高级编程之：`JavaScript` 的 `Class Private Methods`：其在 `JavaScript` 类中的实现。

admin
Tue, 26 Aug 2025 12:40:45 +0000
Javascript, class, javascript, private

各位靓仔靓女们，晚上好！我是今晚的特邀讲师，咱们今晚就来聊聊JavaScript里那些“藏得深”的家伙们——Class Private Methods，也就是类私有方法。保证让你们听完之后，感觉自己好像掌握了某种神秘力量，在代码世界里也能“为所欲为”（当然，是在规则允许的范围内哈！）。 开场白：JavaScript的“小秘密” 在JavaScript的世界里，一直以来都存在着一个关于“隐私”的讨论。毕竟，谁还没点不想让别人知道的秘密呢？在面向对象编程中，封装是一种重要的原则，它允许我们将数据和方法打包在一起，并控制对它们的访问。但JavaScript的早期版本在实现真正的私有性方面一直比较挣扎。 直到ES2015（ES6）引入了class语法，虽然它提供了更加面向对象的语法糖，但仍然没有直接支持私有成员。于是，各种“伪私有”的技巧应运而生，比如使用命名约定（例如，以下划线_开头的属性或方法），或者使用闭包来模拟私有性。但是，这些方法都不能阻止外部代码访问这些“私有”成员。 直到ES2022，JavaScript终于迎来了真正的私有成员——使用#前缀。今天，我们就来深入探讨这个神奇的#， …

继续阅读“JavaScript内核与高级编程之：`JavaScript` 的 `Class Private Methods`：其在 `JavaScript` 类中的实现。”

The post JavaScript内核与高级编程之：`JavaScript` 的 `Class Private Methods`：其在 `JavaScript` 类中的实现。 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

各位靓仔靓女们，晚上好！我是今晚的特邀讲师，咱们今晚就来聊聊JavaScript里那些“藏得深”的家伙们——Class Private Methods，也就是类私有方法。保证让你们听完之后，感觉自己好像掌握了某种神秘力量，在代码世界里也能“为所欲为”（当然，是在规则允许的范围内哈！）。

开场白：JavaScript的“小秘密”

在JavaScript的世界里，一直以来都存在着一个关于“隐私”的讨论。毕竟，谁还没点不想让别人知道的秘密呢？在面向对象编程中，封装是一种重要的原则，它允许我们将数据和方法打包在一起，并控制对它们的访问。但JavaScript的早期版本在实现真正的私有性方面一直比较挣扎。

直到ES2015（ES6）引入了class语法，虽然它提供了更加面向对象的语法糖，但仍然没有直接支持私有成员。于是，各种“伪私有”的技巧应运而生，比如使用命名约定（例如，以下划线_开头的属性或方法），或者使用闭包来模拟私有性。但是，这些方法都不能阻止外部代码访问这些“私有”成员。

直到ES2022，JavaScript终于迎来了真正的私有成员——使用#前缀。今天，我们就来深入探讨这个神奇的#，看看它如何让我们的JavaScript代码更加安全、可靠。

第一部分：JavaScript Class的“前世今生”

在深入了解私有方法之前，我们先简单回顾一下JavaScript Class的基础知识，以及之前实现“伪私有”的方式。

ES5及之前：原型链上的“自由恋爱”

在ES5及更早的版本中，JavaScript并没有class关键字。我们通常使用函数和原型链来模拟类和继承。

// 构造函数
function Person(name) {
  this.name = name; // 公有属性
  this._age = 30;  // 约定：以下划线开头表示“私有”，但实际上还是可以访问
}

// 方法
Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.name);
};

// 实例
var person = new Person("Alice");
person.greet(); // 输出: Hello, my name is Alice
console.log(person._age); // 输出: 30 (虽然我们约定它是私有的，但仍然可以访问)

可以看到，即使我们使用_前缀来暗示_age是私有的，但它仍然可以被外部代码访问。这就像在微信群里公开说“这是我的小秘密”，但谁都能看到。

ES6：class语法糖的“甜蜜陷阱”

ES6引入了class关键字，让JavaScript的面向对象编程更加简洁。但是，它仍然没有提供真正的私有成员。

class Person {
  constructor(name) {
    this.name = name;
    this._age = 30; // 约定：以下划线开头表示“私有”，但实际上还是可以访问
  }

  greet() {
    console.log("Hello, my name is " + this.name);
  }
}

const person = new Person("Bob");
person.greet(); // 输出: Hello, my name is Bob
console.log(person._age); // 输出: 30 (仍然可以访问)

虽然class语法让代码更易读，但本质上它仍然是基于原型链的。_age属性仍然可以被外部代码访问，只是看起来更像Java或C++的类了。

闭包：一种“曲线救国”的方案

为了实现更严格的私有性，我们可以使用闭包。

function createPerson(name) {
  let age = 30; // 闭包内的变量

  return {
    greet: function() {
      console.log("Hello, my name is " + name + ", and I am " + age + " years old.");
    },
    getAge: function() {
      return age; // 提供一个访问age的接口
    }
  };
}

const person = createPerson("Charlie");
person.greet(); // 输出: Hello, my name is Charlie, and I am 30 years old.
console.log(person.age); // 输出: undefined (无法直接访问age)
console.log(person.getAge()); // 输出: 30

在这个例子中，age变量被包含在createPerson函数的作用域内，外部代码无法直接访问。但是，这种方法需要在创建对象时使用函数，而不是使用class语法，并且每次创建对象都会创建一个新的闭包，可能会影响性能。而且，如果要实现继承，会变得非常复杂。

第二部分：#的“横空出世”：真正的私有方法

ES2022引入了#前缀，终于让JavaScript拥有了真正的私有成员。使用#定义的属性或方法只能在类内部访问，外部代码无法访问，甚至子类也无法访问。这就像给你的小秘密上了把锁，钥匙只有你自己有。

定义私有属性和方法

class Person {
  #name; // 私有属性
  #age = 30; // 私有属性，带默认值

  constructor(name) {
    this.#name = name;
  }

  greet() {
    console.log("Hello, my name is " + this.#name + ", and I am " + this.#age + " years old.");
    this.#secretMethod(); // 内部可以访问私有方法
  }

  #secretMethod() { // 私有方法
    console.log("This is a secret method.");
  }

  getAge() {
    return this.#age; // 提供一个访问age的公共接口
  }
}

const person = new Person("David");
person.greet(); // 输出: Hello, my name is David, and I am 30 years old. This is a secret method.
console.log(person.name); // 输出: undefined (无法直接访问私有属性)
console.log(person.#name); // 报错: Private field '#name' must be declared in an enclosing class
person.#secretMethod(); // 报错: Private field '#secretMethod' must be declared in an enclosing class
console.log(person.getAge()); // 输出: 30

在这个例子中，#name和#age是私有属性，#secretMethod是私有方法。外部代码无法直接访问它们。如果尝试访问，会抛出SyntaxError。

私有属性和方法的限制

只能在类内部访问： 私有属性和方法只能在定义它们的类内部访问。

子类也无法访问： 即使子类继承了父类，也无法访问父类的私有属性和方法。

必须在类内部声明： 私有属性必须在类内部声明，否则会抛出SyntaxError。

不能使用delete操作符删除私有属性： 尝试删除私有属性会抛出SyntaxError。

私有属性和方法的优点

真正的封装： 实现了真正的封装，防止外部代码意外修改对象的状态。

代码的健壮性： 提高了代码的健壮性，减少了因外部代码错误使用而导致的问题。

代码的可维护性： 提高了代码的可维护性，因为我们可以放心地修改私有属性和方法的实现，而不用担心影响外部代码。

第三部分：私有方法在实际开发中的应用场景

那么，在实际开发中，我们应该在什么情况下使用私有方法呢？

隐藏内部实现细节： 当我们有一些不希望暴露给外部的内部实现细节时，可以使用私有方法。例如，一个计算器类可能有一些内部的辅助方法来处理复杂的计算逻辑，这些方法不应该被外部代码直接调用。

class Calculator {
  #add(x, y) { // 私有方法，用于内部加法运算
    return x + y;
  }

  #subtract(x, y) { // 私有方法，用于内部减法运算
    return x - y;
  }

  calculate(operation, x, y) {
    switch (operation) {
      case "+":
        return this.#add(x, y);
      case "-":
        return this.#subtract(x, y);
      default:
        throw new Error("Invalid operation");
    }
  }
}

const calculator = new Calculator();
console.log(calculator.calculate("+", 10, 5)); // 输出: 15
// calculator.#add(10, 5); // 报错: Private field '#add' must be declared in an enclosing class

防止外部代码修改对象的状态： 当我们有一些属性不希望被外部代码直接修改时，可以使用私有属性，并提供公共的getter和setter方法来控制对这些属性的访问。

class Counter {
  #count = 0; // 私有属性

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

  decrement() {
    this.#count--;
  }

  getCount() {
    return this.#count; // 提供一个公共的getter方法
  }

  // 没有提供setter方法，防止外部代码直接修改#count
}

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

实现复杂的内部逻辑： 当我们需要在类内部实现一些复杂的逻辑时，可以将这些逻辑分解成多个私有方法，使代码更加清晰易懂。

class Order {
  #items = [];
  #shippingCost = 10;

  addItem(item) {
    this.#items.push(item);
  }

  #calculateTotal() { // 私有方法，用于计算总价
    let total = 0;
    for (const item of this.#items) {
      total += item.price;
    }
    return total;
  }

  #applyDiscount(total) { // 私有方法，用于应用折扣
    if (total > 100) {
      return total * 0.9; // 满100减10%
    }
    return total;
  }

  getTotal() {
    const total = this.#calculateTotal();
    const discountedTotal = this.#applyDiscount(total);
    return discountedTotal + this.#shippingCost;
  }
}

const order = new Order();
order.addItem({ name: "Product 1", price: 50 });
order.addItem({ name: "Product 2", price: 60 });
console.log(order.getTotal()); // 输出: 109 (50 + 60 = 110, 打9折后为99, 加上运费10)

第四部分：#与Symbol的“爱恨情仇”

在ES6中，Symbol也可以用来实现“伪私有”属性。Symbol是一种原始数据类型，它的特点是唯一性，这意味着每次调用Symbol()都会创建一个新的、唯一的Symbol值。我们可以使用Symbol作为属性名，因为外部代码很难猜到Symbol的值，从而实现一定的私有性。

const _name = Symbol("name"); // 创建一个唯一的Symbol

class Person {
  constructor(name) {
    this[_name] = name;
  }

  greet() {
    console.log("Hello, my name is " + this[_name]);
  }
}

const person = new Person("Eve");
person.greet(); // 输出: Hello, my name is Eve
console.log(person[_name]); // 输出: Eve (仍然可以访问，但需要知道Symbol的值)

const symbols = Object.getOwnPropertySymbols(person);
console.log(symbols); // 输出: [Symbol(name)] (可以获取Symbol属性)

虽然Symbol可以提供一定程度的私有性，但它并不是真正的私有。我们可以使用Object.getOwnPropertySymbols()方法来获取对象的所有Symbol属性，从而访问到这些“私有”属性。

那么，#和Symbol有什么区别呢？

特性 | # (私有属性/方法) | Symbol (伪私有属性)
访问性 | 只能在类内部访问 | 可以通过Object.getOwnPropertySymbols()访问
继承 | 子类无法访问 | 子类可以访问
唯一性 | 类级别唯一 | 全局唯一
运行时错误 | 访问错误会抛出SyntaxError | 可以访问，但可能导致意外行为
真正的私有性 | 是 | 否

总的来说，#提供了真正的私有性，而Symbol只是一种“约定”，可以隐藏属性名，但不能阻止外部代码访问。

第五部分：关于私有方法的“一些思考”

虽然私有方法可以提高代码的安全性、健壮性和可维护性，但它们也带来了一些问题。

测试的困难： 由于私有方法无法直接从外部访问，因此测试起来比较困难。我们需要通过公共方法来间接测试私有方法的逻辑。

继承的限制： 私有方法无法被子类继承，这可能会限制代码的复用性。

过度使用： 过度使用私有方法可能会导致代码过于复杂，难以理解和维护。

因此，在使用私有方法时，我们需要权衡利弊，谨慎使用。一般来说，只有那些真正需要隐藏的内部实现细节才应该使用私有方法。

总结：#的“未来可期”

ES2022引入的#前缀，标志着JavaScript在实现真正的私有性方面迈出了重要的一步。它为我们提供了一种更加安全、可靠的方式来编写面向对象的代码。虽然私有方法也存在一些问题，但它们带来的好处远远大于坏处。

相信在未来的JavaScript开发中，#将会得到越来越广泛的应用，帮助我们构建更加健壮、可维护的代码。

好了，今天的讲座就到这里。希望大家通过今天的学习，能够更好地理解和使用JavaScript的私有方法。记住，#不仅仅是一个符号，它代表着一种更加安全、可靠的编程方式。

感谢大家的聆听，我们下次再见！