Public, private and protected class visibility modes in javascript

Public, private and protected class visibility modes in javascript

Public Class

As part of ES2015, it supports public fields (no field syntax), public getters / setters and public methods, as well as public static getters / setters and public static methods.

Here is an example that includes all the methods mentioned above:

class Person {<!-- -->
  constructor(firstName, lastName) {<!-- -->
    this.firstName = firstName; // Public field
    this.lastName = lastName; // Public field
  }

  // public getter
  get fullName() {<!-- -->
    return `${<!-- -->this.firstName} ${<!-- -->this.lastName}`;
  }

  // public setter
  set fullName(value) {<!-- -->
    const parts = value.split(" ");
    this.firstName = parts[0];
    this.lastName = parts[1];
  }

  // public method
  introduceYourselfTo(other) {<!-- -->
    const name = other.firstName  other;
    console.log(`Hello ${<!-- -->name}! My name is ${<!-- -->this.fullName}.`);
  }

  // public static getter
  static get typeName() {<!-- -->
    return "Person";
  }

  // public static method
  static fromJSON(json) {<!-- -->
    return new Person(json.firstName, json.lastName);
  }
}

const john = new Person("leo", "lau");
const jane = Person.fromJSON({<!-- --> firstName: "leo", lastName: "lau" });

john.introduceYourselfTo('jack');
</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

This version of class is familiar to most js developers today. It was the first solid set of class features of the language, but several things were missing. One of the missing features is the syntax of the fields. In subsequent versions of the language, this problem was solved by enabling public fields and public static field syntax. Here’s an example:

class Person {<!-- -->
  age = 0; //Field syntax
  static typeName = "Person"; // Syntax of public fields
  shareYourAge() {<!-- -->
    console.log(`I am ${<!-- -->this.age} years old.`);
  }
}
const john = new Person("leo", "lau");
leo.age = 25;
leo.shareYourAge();

While designing the decorator, a supporting function for the automatic accessor was also developed. Just use the new accessor keyword:

class Person {<!-- -->
  accessor age = 0;
}
const john = new Person("leo", "lau");
leo.age = 25;
leo.shareYourAge();

Private Class

In July 2021, major browsers will support private class, bringing protected private functions into the language. Private members all start with the # character and can only be called statically. Here is an example:

class Person {<!-- -->
  #firstName; // Private property
  #lastName; // Private property

  constructor(firstName, lastName) {<!-- -->
    this.#firstName = firstName;
    this.#lastName = lastName;
  }

  get firstName() {<!-- --> return this.#firstName; }
  get lastName() {<!-- --> return this.#lastName; }
  get fullName() {<!-- -->
    return `${<!-- -->this.firstName} ${<!-- -->this.lastName}`;
  }

  introduceYourselfTo(other) {<!-- -->
    const name = other.firstName  other;
    console.log(`Hello ${<!-- -->name}! My name is ${<!-- -->this.fullName}.`);
  }

  static fromJSON(json) {<!-- -->
    return new Person(json.firstName, json.lastName);
  }
}
</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

By combining private fields with public getters, js now also supports platform-protected read-only properties shown above.

Private Constructors

Fields, getters, setters and methods, instances and statics can be made private, but constructors cannot. But a pattern can be used to prevent unauthorized calls to the constructor.

class SomeSingletonService {<!-- -->
  static #key = {<!-- -->};
  static instance = new SomeSingletonService(this.#key);
 
  constructor(key) {<!-- -->
    if (key !== SomeSingletonService.#key) {<!-- -->
      throw new TypeError("SomeSingletonService is not constructable.");
    }
  }
}

The important detail in this pattern is that we hold a private static key as the first parameter of the constructor. If the key is not provided to unlock the constructor, an exception is thrown. Now our class can create instances at will using its private key, but any other party trying to instantiate via the constructor is blocked.

Protected Class

Although JavaScript does not provide this feature, we can use some methods to handle private constructor methods. Here’s how it works:

// Parent class:
function Question(key) {<!-- -->
  return class {<!-- -->
    #answer = 42;
    answer(shareKey) {<!-- -->
      if (shareKey === key) {<!-- -->
        return this.#answer;
      }
      throw new TypeError("Access Denied");
    }
  }
}

const key = {<!-- -->};
// Subclass
class DeepThought extends Question(key) {<!-- -->
  get #answer() {<!-- -->
    return this.answer(key);
  }
  tellMeTheAnswer() {<!-- -->
    console.log(this.#answer);
  }
}
const dm = new DeepThought();
dm.tellMeTheAnswer();
</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

We first create a factory function Question that takes a key as input and will use that key to check access to any shared value it provides. We then create a unique key and pass it to the superclass factory as part of the subclass definition. In the subclass, we create a private property for accessing the shared value, which internally calls the superclass shared method using a predefined key. To avoid making protected members public, we also make our subclasses private.

Use decorators

The above code is for protected scenarios as well as arbitrary sharing, just by sharing a secret. However, some template code is not particularly expressive and can even be a bit confusing. We can do this by using the decorator:

function Question(share) {<!-- -->
  return class {<!-- -->
    @share accessor #answer = 42;
  }
}

class DeepThought extends Question(share) {<!-- -->
  @access accessor #answer;

  tellMeTheAnswer() {<!-- -->
    console.log(this.#answer);
  }
}

const dm = new DeepThought();
dm.tellMeTheAnswer();
</code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">

Using decorators makes it clearer what values are shared by superclasses and what values are accessible by subclasses. It also eliminates some of the confusion caused by similar names in the previous version and is more suitable for multiple members.

The technique to achieve this is to use a factory function for two decorators that share a common private accessor store.