Private Accessors

Happy Halloween! I'm going to continue on with another ES2021 feature, called Private Accessors. This feature is similar and related to Private Class Methods.

Accessors

When we talk about Accessors, we're talking about Getters and Setters. A Getter allows you to retrieve the value of an object property, and a Setter allows you to define the value of an object property.

class User {
  constructor() {
    this.firstName = "";
    this.lastName = "";
  }
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(" ");
  }
}

const joey = new User();

In the code above we have defined a User class. By default, a User will have two properties, firstName and lastName, both initially equal to empty strings.

Below the User class definition we are creating a new User and setting it to the variable joey.

Now we can use a Setter to assign values to the firstName and lastName properties on the joey object. Here is our Setter method:

set fullName(name) {
  [this.firstName, this.lastName] = name.split(" ");
}

The fullName Setter accepts a name parameter, which will be a first and last name, like "Joey Reyes". We use the .split() method to create an array of this name, which looks like: ["Joey", "Reyes"]. We then use Array Destructuring to set the first item in this array to be the value of the firstName property and the second item in the array to be the value of the lastName property.

We actually call this method like so:

joey.fullName = "Joey Reyes";

By assigning the value of the method with the = equals sign, we are calling it as a Setter.

With the firstName and lastName property values defined by the Setter, we can now use the Getter function (of the same name, fullName) to retrieve those values:

joey.fullName;
// Expected result: "Joey Reyes"

We can also directly call the firstName and lastName properties to get their individual values:

joey.firstName;
// Expected result: "Joey"
joey.lastName;
// Expected result: "Reyes"

Private Accessors

Now that we've reviewed what good, old fashioned Accessors are, let's look at Private Accessors, which are Getters and Setters that run privately and work with private values.

class User {
  #password;

  constructor() {
    this.firstName = "";
    this.lastName = "";
  }
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(" ");
    this.#generatedPassword = `${this.firstName.toLowerCase()}123`;
    console.log(this.#generatedPassword);
  }
  get #generatedPassword() {
    return `πŸ” ${this.#password} πŸ”`;
  }
  set #generatedPassword(password) {
    this.#password = password;
  }
}

const joey = new User();

joey.fullName = "Joey Reyes";

The code above has added several things onto the first code example. We have added a private field called #password, which will store a password that we generate.

Like with Private Class Methods, Private Fields and Private Accessors (Getters and Setters) use a # hash character to make those fields or accessors private.

Here is our new Private Setter:

set #generatedPassword(password) {
  this.#password = password;
}

This Private Setter accepts a parameter, password, and sets the value of that passed argument to be the value of the #password private field.

We also have a new Private Getter:

get #generatedPassword() {
  return `πŸ” ${this.#password} πŸ”`;
}

This Private Getter simply returns the value of the #password private field, and returns it with some lock and key emojis.

Lastly, we are actually calling the new Private Getter and Setter methods within the public fullName Setter function:

set fullName(name) {
  [this.firstName, this.lastName] = name.split(" ");
  this.#generatedPassword = `${this.firstName.toLowerCase()}123`;
  console.log(this.#generatedPassword);
}

After destructuring the firstName and lastName properties, we create a new password value by setting the value of firstName to lowercase and appending it with the number 123. This then uses the #generatedPassword Setter to define this name/number string as the value of #password. We then use the Getter #generatedPassword within a console.log() statement, which gives us the string: πŸ” joey123 πŸ”.

So why do all of this? Well, say we run the following code:

class User {
  #password;

  constructor() {
    this.firstName = "";
    this.lastName = "";
  }
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(" ");
    this.#generatedPassword = `${this.firstName.toLowerCase()}123`;
    console.log(this.#generatedPassword);
  }
  get #generatedPassword() {
    return `πŸ” ${this.#password} πŸ”`;
  }
  set #generatedPassword(password) {
    this.#password = password;
  }
}

const joey = new User();

joey.fullName = "Joey Reyes";
joey.#generatedPassword;
// Expected result: SyntaxError

If we try to call the #generatedPassword Private Getter in the same manner as the fullName Public Getter, we get a Synax Error because it's Private Accessor. We cannot call any of the Private functionality outside the scope of the class itself. This means that that data and functionality is protected.

From MDN: Class fields are public by default, but private class members can be created by using a hash # prefix. The privacy encapsulation of these class features is enforced by JavaScript itself.

See more examples and documentation here.