Understanding the Power of # in JavaScript Classes: Private Fields Demystified

Understanding the Power of # in JavaScript Classes: Private Fields Demystified
Photo by Annie Spratt / Unsplash

JavaScript classes have come a long way in providing developers with tools to write clean, maintainable, and encapsulated code. Among the many features introduced, private fields, marked by the # symbol, stand out as a game-changer. They help ensure that certain data or methods are truly private and inaccessible from outside the class. Let’s dive deep into this feature and explore why it’s so valuable, some caveats, and considerations you might have overlooked.

What Are Private Fields in JavaScript?

Private fields are a way to restrict direct access to properties or methods of a class from outside its scope. By prefixing a field name with #, you declare it private. This means it can only be accessed or modified within the class itself.

Here's a basic example:

class Person {
  #name; // private field

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

  getName() {
    return this.#name;
  }
}

const john = new Person("John Doe");
console.log(john.#name); // ❌ SyntaxError: Private field '#name' must be declared in an enclosing class
console.log(john.getName()); // ✅ "John Doe"

Key Points to Remember:

  1. Truly Private: Unlike _name (a common convention to imply private fields), #name cannot be accessed or tampered with directly outside the class.
  2. Unique to Instances: Each instance of the class gets its own unique private field, ensuring no overlap or unintended sharing of private data.

Why Use Private Fields?

  1. Encapsulation and Security
    Private fields help protect sensitive data, ensuring it's not accidentally or maliciously modified. This is particularly useful for APIs, libraries, or components where you want to expose only specific functionality.
  2. Improved Readability
    Using # makes it immediately clear which fields are private, reducing confusion for developers reviewing the code.
  3. Avoids Conflicts
    With private fields, you avoid naming collisions with properties from outside the class or subclasses.

What You Might Be Missing

1. Private Methods with #

Private fields aren’t just for data; they can also be applied to methods. This ensures that helper functions or internal logic stay hidden:

class Calculator {
  #add(a, b) {
    return a + b;
  }

  computeSum(a, b) {
    return this.#add(a, b);
  }
}

const calc = new Calculator();
console.log(calc.computeSum(2, 3)); // ✅ 5
console.log(calc.#add(2, 3)); // ❌ SyntaxError

2. Cannot Be Accessed Dynamically

You cannot access private fields using square bracket notation (obj["#field"]). This prevents accidental or intentional access. However, this also means some dynamic programming patterns won’t work with private fields.

3. Not Inherited by Subclasses

Private fields are not inherited. Each class must declare its own private fields, even if the field exists in a parent class.

class Parent {
  #secret = "parent secret";

  getSecret() {
    return this.#secret;
  }
}

class Child extends Parent {
  getParentSecret() {
    return this.#secret; // ❌ SyntaxError
  }
}

4. Impact on Performance

Since private fields use a unique internal slot for each instance, they can have a slight memory overhead compared to public fields. While this is negligible for most applications, it’s worth considering in performance-critical code.

Alternatives and Complementary Approaches

1. WeakMaps for Privacy

Before private fields, developers used WeakMap to achieve similar privacy. While still a valid approach, it’s more verbose and less readable than using #:

const privateData = new WeakMap();

class Person {
  constructor(name) {
    privateData.set(this, { name });
  }

  getName() {
    return privateData.get(this).name;
  }
}

2. TypeScript’s private Keyword

If you’re using TypeScript, the private keyword achieves similar goals but is enforced only at compile-time. JavaScript’s # provides runtime privacy, offering a stricter guarantee.

Best Practices When Using #

  1. Use Meaningful Names: Even though they’re private, make sure private field names are descriptive to maintain code readability.
  2. Combine with Getters and Setters: Expose controlled access to private fields using getters and setters.
  3. Don’t Overuse: Not everything needs to be private. Overusing private fields can lead to bloated code. Use them only when necessary.
  4. Document Your Code: Clearly document the intent and use of private fields to aid team members or future maintainers.

When to Avoid Private Fields

  • Library Code with Dynamic Requirements: If you’re building a library where fields might need dynamic access or patching, # may not be suitable.
  • Interoperability Concerns: Some older tools or frameworks may not fully support private fields yet. Ensure compatibility before adopting.

Finally

The # syntax for private fields in JavaScript is a powerful addition that enhances encapsulation and security in class-based designs. By enforcing true privacy, it protects your code from external interference and accidental misuse. However, it’s essential to use this feature thoughtfully, balancing privacy with flexibility and maintainability.

As JavaScript continues to evolve, adopting modern features like private fields can improve code quality and future-proof your projects. If you haven’t started using # yet, now is the time to explore its potential!

Have you used private fields in your projects yet? Share your experience and tips on the comment below!

Support Us