JavaScript Private and Protected Class Members
In today's digital landscape, JavaScript continues to be a cornerstone of web development, powering dynamic and interactive user experiences. As developers,
A class often needs to hide some of its inner workings so that outside code can't read or change them directly. This page covers the two ways JavaScript lets you do that: truly private members declared with a # prefix, and the _ underscore convention developers use to mark members as "protected" (intended only for the class and its subclasses).
You'll see when to reach for each, why # fields are genuinely inaccessible from outside, and how to combine private fields with getters and setters to expose a controlled, validated interface. If classes are new to you, start with the Class basic syntax chapter first.
Introduction to Encapsulation in JavaScript
Encapsulation is a fundamental principle of object-oriented programming (OOP): it bundles the data (variables) and the methods (functions) that operate on that data into a single unit — an object — and controls access to that object's internals. The goal is to expose a small, deliberate public interface while hiding the implementation details behind it.
Why does this matter? When internal state is hidden, outside code can't put your object into an invalid state, and you're free to change how the class works internally without breaking the code that uses it. In JavaScript, encapsulation is achieved with private # fields and methods. JavaScript has no native protected modifier, so developers simulate it with a naming convention (the _ prefix), as explained below.
Private Properties and Methods
A private class member is declared with a # prefix in its name. It can only be accessed from inside the class body — any attempt to read or write it from outside is a hard syntax error, not just undefined. This is real, language-enforced privacy.
A field must be declared in the class body before it can be used (you can't create a # field on the fly inside the constructor the way you can with a public property). Note also that private # fields are non-enumerable: they don't appear in Object.keys(), for...in, or JSON.stringify() output.
Why # is truly private
Unlike the underscore convention, a # field is invisible outside the class. You can't reach it through user.#name, through bracket access like user[\"#name\"], or through Object.keys(). The first form is a syntax error; the others simply don't find the field.
Private Methods
Methods can be private too: prefix the method name with #. A private method is useful for internal helpers that callers should never invoke directly — for example, validation or formatting logic that supports the public API but isn't part of it.
Protected Properties and Methods (the _ convention)
JavaScript has no protected keyword. By convention, a member meant to be used by the class and its subclasses — but not by outside code — is prefixed with a single underscore _. This is purely a signal to other developers; an _ member stays fully readable and writable from anywhere. Choose it when subclasses need access (a # field is not accessible from subclasses), and accept that the protection is by agreement, not enforcement.
Because _ members are inherited like any normal property, a subclass can rely on them. This is the practical reason to use the convention instead of # when designing for inheritance:
Accessor-Based Encapsulation: Best Practices
When incorporating private and protected properties and methods into your JavaScript projects, consider the following best practices to maximize their benefits:
- Use private fields for sensitive or invariant data: Store anything that should not be touched directly — internal counters, cached values, raw state — as a
#field. This guarantees integrity and prevents unintended side effects. - Leverage getters and setters: Keep the field private and expose it through a getter and setter. The setter is your chance to validate or transform input before it reaches the private field, so the object can never hold an invalid value.
In the example below, #age can only be changed through the setter, which rejects negative numbers:
You can also expose read-only state by defining a getter with no matching setter — the consumer can read the value but has no way to overwrite the underlying private field.
- Apply protected members for inheritance: Use protected properties and methods when you intend for them to be accessible within subclasses. This strategy facilitates a more flexible and hierarchical structure in your applications.
Advanced Techniques and Patterns
Beyond the basics, JavaScript allows for sophisticated patterns and techniques to further encapsulate and structure your code effectively:
- Module pattern: Utilize closures and immediately invoked function expressions (IIFE) to create private scopes.
- Factory functions: These functions return new objects, allowing for private data through closures, without the need for the
newkeyword. - Proxies: JavaScript Proxies can be used to create protective wrappers around objects, controlling access to their properties and methods.
Conclusion
Use # fields and methods when you want genuine, enforced privacy that no outside code can reach. Use the _ underscore convention when subclasses need access and the protection can be by agreement. Combine private fields with getters and setters to expose a controlled, validated interface — and read-only getters when state should be observable but not mutable.
Related topics
- Class basic syntax — declaring classes, constructors, and methods.
- Property getters and setters — the accessor pattern used throughout this page.
- Static properties and methods — members that belong to the class itself, including private static fields.