Static, Private, and Getter/Setter Members
Go beyond basic classes with static methods and properties, true private fields using #, and getter/setter methods that let you control how a property is read and written.
Reading Time
18 min
Lesson
Lesson 41 of 48
Beyond the Basics
A basic class with a constructor and instance methods covers most everyday needs, but JavaScript classes support a few more advanced members that solve specific, common problems: static members for behavior that belongs to the class itself rather than any one instance, private fields for genuine encapsulation, and getters/setters for controlling how a property is read or written. Each of these earns its place by making certain patterns much cleaner to express.
Static Methods and Properties
A member marked with the static keyword belongs to the class itself, not to any individual instance — you call it as ClassName.member() rather than instance.member(). Static members are useful for utility functions related to the class's purpose (like a factory that builds instances in a special way) or for shared data that all instances should be aware of, but that doesn't belong to any single one of them.
A Static Method and a Static Property
Console Output
Click “Run” to see the console output here.
Static Members Are Not Inherited to Instances
Static properties and methods live only on the class itself. Trying to access them through an instance, like alice.totalUsers, will not work — you must always go through the class name, or through this inside another static method.
Private Fields and Methods with #
Before the # syntax, JavaScript had no true way to make a class member private — properties assigned with this were always accessible from outside the class, just by convention or a leading underscore that anyone could ignore. Prefixing a field or method name with # makes it genuinely private: it can only be accessed from inside the class body, and even attempting to read it from outside throws a SyntaxError rather than quietly returning undefined.
A Truly Private Field
Console Output
Click “Run” to see the console output here.
Private Fields Must Be Declared in the Class Body
Unlike regular instance properties, you can't create a private field on the fly by just assigning this.#something inside a method — every private field must first be declared directly in the class body (even if only as #balance; with no initial value), or JavaScript throws a SyntaxError.
Getters and Setters
The get and set keywords let you define a property that looks like a plain field from the outside, but is actually backed by a method. A getter runs custom code whenever the property is read; a setter runs custom code whenever it's assigned. This is how you validate incoming values, compute a derived value on the fly, or expose a controlled view onto a private field, all while keeping the calling code as simple as account.balance rather than account.getBalance().
Getters and Setters on Their Own
Console Output
Click “Run” to see the console output here.
Combining Them: a Private Field Behind a Getter/Setter
The most practical pattern combines all three features: a private field holds the real data, and a public getter/setter pair controls how that data is read and written — including validation. Outside code interacts with a clean property name and never touches the private field directly, but every read and write can be checked or transformed.
A Validated Property Backed by a Private Field
Console Output
Click “Run” to see the console output here.
Class Feature Cheat Sheet
| Feature | Syntax | Called On | Purpose |
|---|---|---|---|
| Static method/property | static name() / static name = value | The class itself | Shared behavior/data not tied to one instance |
| Private field/method | #name | this, inside the class only | True encapsulation — inaccessible from outside |
| Getter | get name() | Read like a property: obj.name | Compute or expose a value on read |
| Setter | set name(value) | Assign like a property: obj.name = x | Validate or transform a value on write |
When to Reach for Each Feature
- Use static members for factory methods or class-wide counters/configuration
- Use private fields whenever internal state should never be touched from outside the class
- Use a getter when you want a computed, read-only-feeling property
- Use a setter alongside a private field when you need to validate incoming values
- Combine private fields with getters/setters for controlled, safe access to internal data
Private Fields Are Also useful for Preventing Naming Collisions
Because # fields are scoped to the class body, a subclass and a parent class can each safely use a private field with the same name without any conflict — they are completely separate. This is one more advantage private fields have over plain this properties, which live in one shared namespace on the instance.
Getters/Setters Can Hide Expensive Work
Because a getter is called with simple property syntax (obj.value), it's easy to forget that it might be doing real computation behind the scenes, unlike a plain property lookup. Avoid putting slow or side-effect-heavy logic inside a getter — callers reasonably expect reading a property to be fast and safe to do repeatedly.