Privacy and Static Members
Introduction: Protecting Your Data
In our first lesson, we learned how to create blueprints for objects using the class keyword. We used the constructor to set initial properties, like a person's name or a book's title. While that approach is a great start, it has a small problem: any part of your program can reach inside your object and change that data. For example, if you have a bank account object with a balance, you probably do not want someone to accidentally set that balance to a negative number or a piece of text.
In professional software development, we use a concept called encapsulation. This means we bundle the data and the methods that work on that data together, but we also hide the "innards" of the object from the outside world. By the end of this lesson, you will know how to use private fields to hide sensitive data, and how to use getters and setters to provide a safe way for others to interact with that data.
Private Instance Fields With
Up until recently, JavaScript did not have a way to truly hide data inside a class. Developers often used a naming convention, like putting an underscore before a variable name (such as _balance), to tell others, "Please do not touch this." However, this was just a suggestion and did not actually stop anyone from changing the value.
Modern JavaScript now includes private fields. To make a property private, you must declare it at the top of your class using the # symbol before its name. Once a property is marked with a #, it can only be accessed or changed inside the code of that specific class. If you try to access it from outside the class, JavaScript will throw a SyntaxError.
In the code above, the #balance field is declared before the constructor. This tells the JavaScript engine that this piece of data is "private" to the Account class. When we create an instance called myAccount, the balance is safely tucked away. Because the language itself enforces this rule, it is impossible for another developer to accidentally overwrite the balance from outside the class definition.
Getters: Controlled Read Access
Now that we have successfully hidden our data, you might wonder how we can actually see it. If we cannot access #balance directly, we need a way to "ask" the object for that information. We do this using a getter. A getter is a special method that looks like a regular property when you use it, but it is actually a function that runs behind the scenes.
We define a getter by using the get keyword followed by the name we want to use for the property. This allows us to share the data while still keeping the original private field protected.
Output:
In this example, we created a getter called balance. When we write myAccount.balance, JavaScript automatically runs the code inside the get balance() block and returns the value of #balance. Notice that when we call the getter, we do not use parentheses like a normal function; we treat it exactly like a property. This makes our code clean and easy to read.
Setters: Controlled Write Access With Validation
While getters allow us to read data, setters allow us to change it. You might ask, "If we are just going to let people change the value, why make it private in the first place?" The answer is validation. A setter allows us to check the new value before we actually save it to our private field. This ensures our object always stays in a valid state—even when it is first created.
It is also worth noting that if you define a getter without a matching setter, you create a read-only property. Any attempt to assign a new value to it will silently fail in non-strict mode, or throw a TypeError in strict mode. This is a useful pattern when you want to expose a value but guarantee it can never be changed from the outside.
We define a setter using the set keyword. Inside the setter, we can write logic to make sure the data being provided is correct. To ensure the input is a valid number (and not something like NaN, Infinity, or a string), we use Number.isFinite().
In this logic, the setter acts as a "guard." By calling this.balance = balance inside the constructor, we make sure that even the very first value passed to the class is checked. This prevents our account from ever having a negative balance or an invalid numeric value.
Static Properties And Methods
Sometimes, you need to store data that belongs to the class itself rather than to a specific instance. For example, you might want to keep track of how many accounts have been created in total. If you stored this count inside an instance, every account would have its own count starting at one. This is where the static keyword comes in.
Properties and methods marked as static belong to the class blueprint, not to the individual objects created from it. You access these members by using the name of the class itself.
Output:
In the example above, type and getClassName are static. We do not need to use the new keyword to access them. We simply use the class name Account. This is very useful for utility functions or for data that should be shared across every single instance of the class.
Private Static Fields
Just like instance properties, static properties can also be made private. By combining the static keyword with the # symbol, we can create private static fields that belong to the class. A common use for this is to keep track of a "counter" that we do not want anyone outside of the class to modify.
In this snippet, #count is a private static field. It starts at zero. Every time a new account is created, the constructor runs and increments Account.#count. Because it is private, nobody can manually change the count from the outside. We provide a static getter called total so that we can check the number of accounts without being able to change it.
Putting It All Together: The Account Class
Now that we have explored all these individual parts, let’s look at how they work together in a complete, professional example. This Account class uses private fields to protect the balance, a private static field to track the total number of accounts, and accessors to manage how this data is used.
Output:
In this final example, the Account class is fully protected. The #balance is hidden from the outside, but we can still interact with it safely through the balance getter and setter. By calling the setter within the constructor, we ensure that an Account can never be created with an invalid balance. This structure is a perfect example of modern, clean, and safe JavaScript code.
Summary And What's Next
In this lesson, we learned how to move beyond basic classes to create robust and secure objects. We used the # symbol to create private fields that protect our data from unauthorized changes. We implemented getters and setters to control how our data is read and updated, ensuring that our objects stay in a valid state from the moment they are created. Finally, we used the static keyword to manage data that belongs to the entire class rather than just one instance.
Now head into the practice environment to build these structures yourself. You will get to practice writing your own private fields and validation logic, which will help you truly master these concepts. Happy coding!
