JavaScript Object-Oriented Programming: Private Properties

JavaScript Object-Oriented Programming: Private Properties

JavaScript’s object-oriented programming has evolved greatly, especially with the introduction of private properties. Private properties allow developers to hide internal details of an object, making sure that some data stays protected and cannot be accessed or modified from outside the object. This encapsulation concept is important for building robust applications, as it ensures that the internal state of an object cannot be accidentally changed or misused by external code.

In JavaScript, private properties differ from the usual public properties because they are not accessible directly on the object instance. Unlike public properties, which anyone can read or write, private properties remain hidden and can only be accessed or modified inside the object’s own methods. This article will guide you through how to create and use private properties in JavaScript with practical, fun examples using modern syntax and also older closure-based techniques.

Declaring Private Properties with the # Syntax

The modern way to declare private properties in JavaScript classes uses the # symbol before the property name. This syntax was introduced in recent ECMAScript versions and is now widely supported. Private properties declared this way are truly private — they cannot be accessed outside the class, even by mistake.

Let’s start with a simple class called SecretDiary. It will have a private property #entries that stores diary entries. The only way to interact with these entries will be through class methods.

class SecretDiary {

  #entries = [];

  addEntry(entry) {
    this.#entries.push(entry);
  }

  getEntries() {
    return [...this.#entries];
  }

}

const myDiary = new SecretDiary();
myDiary.addEntry("Today I learned about private properties.");
myDiary.addEntry("JavaScript is fun!");

console.log(myDiary.getEntries()); 

In this example, #entries is a private array. We add new diary entries using the addEntry method, and getEntries returns a copy of all entries. Notice we never access #entries outside the class — trying to do so would cause an error.

This approach is clean, clear, and keeps the diary entries hidden from any direct outside access.

Accessing Private Properties inside Class Methods

Private properties are accessible only within the class body, typically inside methods. This means that methods like addEntry and getEntries can freely read and update the private #entries array, but code outside cannot.

For example, if you try:

console.log(myDiary.#entries);

This will throw a syntax error, because JavaScript does not allow access to private properties from outside the class.

Thus, private properties create a controlled interface: only the class itself decides how to use or reveal the internal data, making your code more secure and easier to maintain.

Attempting to Access Private Properties Outside the Class

To demonstrate what happens when you try to peek into private properties from outside, consider this snippet:

try {
  console.log(myDiary.#entries);
} catch (error) {
  console.log("Error:", error.message);
}

When run, this throws an error: Private field '#entries' must be declared in an enclosing class. This means JavaScript strictly enforces privacy, protecting your internal data from accidental or intentional external access.

Private Properties Using Closures (Alternative Approach)

Before the # syntax was introduced, JavaScript developers used closures to create private properties. Closures allow you to hide variables inside a function scope, which external code cannot directly reach.

Here’s an example using a constructor function, creating a MagicBox that stores secret items privately:

function MagicBox() {

  let contents = [];

  this.addItem = function(item) {
    contents.push(item);
  };

  this.getContents = function() {
    return [...contents];
  };

}

const box = new MagicBox();
box.addItem("Ruby");
box.addItem("Emerald");

console.log(box.getContents()); 

In this example, the variable contents exists only inside the MagicBox function’s scope. It’s not accessible directly from the box object, but the public methods addItem and getContents allow interaction with the private data.

Accessing and Modifying Private Properties via Public Methods

Notice in both the SecretDiary and MagicBox examples, private data is always manipulated through public methods. This encapsulation pattern helps prevent misuse and controls how the internal data changes over time.

For instance, you cannot accidentally overwrite the contents of MagicBox or the #entries in SecretDiary without using the designated methods.

Using Private Properties with Inheritance

Private properties declared with the # syntax are truly private to the class they are defined in and are not accessible by subclasses. Let’s see what happens when a subclass tries to access private properties:

class SecretDiary {

  #entries = [];

  addEntry(entry) {
    this.#entries.push(entry);
  }

  getEntries() {
    return [...this.#entries];
  }

}

class FriendDiary extends SecretDiary {

  tryToReadEntries() {

    try {
      return this.#entries;
    } catch (error) {
      return "Cannot access private entries here!";
    }

  }

}

const friendDiary = new FriendDiary();
friendDiary.addEntry("Friend's secret!");

console.log(friendDiary.getEntries()); 
console.log(friendDiary.tryToReadEntries());

In this code, FriendDiary tries to access #entries directly and fails, because private fields are limited to the original class. However, FriendDiary can still use public methods inherited from SecretDiary to interact with the entries safely.

Combining Private Properties with Static Properties or Methods

Static methods belong to the class itself, not to any particular instance, so they cannot access private instance properties directly. For example:

class TreasureChest {

  #goldCoins = 0;

  constructor(coins) {
    this.#goldCoins = coins;
  }

  getGold() {
    return this.#goldCoins;
  }

  static totalGold(chests) {
    return chests.reduce((sum, chest) => sum + chest.getGold(), 0);
  }

}

const chest1 = new TreasureChest(100);
const chest2 = new TreasureChest(200);

console.log(TreasureChest.totalGold([chest1, chest2])); 

Here, the static method totalGold calculates the total gold from multiple instances by calling their public getGold methods, since it cannot access the private #goldCoins directly.

Real-World Fun Example: TreasureChest

To wrap up the concepts, let’s build a fun TreasureChest class that uses private properties to keep its treasures hidden. It will have private #goldCoins and #gems, and methods to add or remove treasure, plus a method to display the treasure count without revealing the exact details.

class TreasureChest {

  #goldCoins = 0;
  #gems = 0;

  constructor(gold, gems) {
    this.#goldCoins = gold;
    this.#gems = gems;
  }

  addGold(amount) {
    this.#goldCoins += amount;
  }

  addGems(amount) {
    this.#gems += amount;
  }

  openChest() {
    return `You have ${this.#goldCoins} gold coins and ${this.#gems} gems inside!`;
  }

}

const chest = new TreasureChest(50, 10);
chest.addGold(25);
chest.addGems(5);

console.log(chest.openChest()); 

In this example, the #goldCoins and #gems cannot be accessed from outside, but the openChest method gives a friendly summary of the treasure inside.

Conclusion

Private properties in JavaScript allow objects to keep internal data hidden from outside access, providing a strong encapsulation tool for cleaner and safer code. The modern # syntax lets you declare true private fields within classes that can only be accessed by the class’s own methods. Alternatively, closures can be used in function constructors to simulate private properties by leveraging function scope.

Through the examples of SecretDiary, MagicBox, and TreasureChest, we explored how to declare, access, and protect private data. We also saw how private properties behave with inheritance and static methods.

Mastering private properties helps you write more secure and maintainable JavaScript code, making your classes smarter and your data safer.

References

If you want to learn more about JavaScript private properties and related concepts, these resources are excellent starting points:

Scroll to Top