Private Members

A private member is a property or method which can only be accessed by methods within the class. As of July 2017, there is no native support for private properties and methods. Symbols can be used to provide 'an extra level of privacy', by preventing the keys/properties of an object from being exposed through some popular methods such as for...in, Object.keys() and JSON.stringify().


let Person = (function () {
  let age = Symbol(); // declared in another module perhaps?

  class Person {
    constructor(n, a) {
      this.name = n;
      this[age] = a;
    }

    introduce() {
      console.log(`My name is ${this.name}. I am ${this[age] - 10}.`);
    }
  }

  return Person;
})();

var j = new Person('Jane', 45);
j.introduce();
console.log(JSON.stringify(j));
console.log(Object.keys(j));
console.log(j[Object.getOwnPropertySymbols(j)[0]]);

My name is Jane. I am 35. {"name":"Jane"} ["name"] 45

Although given an object per se, such properties can still be exposed through Proxy (8.21), Object.getOwnPropertySymbols(), etc., there is no natural means to access them through a few direct methods, which may be sufficient sometimes from an OOP perspective.A more effective alternative is to use a scoped WeakMap (8.15), which is not enumerable at all.


let Person = (function () {
  let privateProps = new WeakMap();

  class Person {
    constructor(name) {
      this.name = name;                   // this is public
      privateProps.set(this, {age: 45});  // this is private
    }

    greet() {   // Here we can access both name and age
      console.log(`I am ${this.name}.
                           I am ${privateProps.get(this).age-10}.`);
    }
  }

  return Person;
})();

let j = new Person('Jane');
j.greet();
console.log(j.privateProps);

I am Jane. I am 35. undefined

Since ECMAScript 2022, private fields using the # syntax are natively supported, making the Symbol- and WeakMap-based workarounds above largely unnecessary. A name prefixed with # (eg. #v2) declares a private field that can only be read or written from inside the body of the class where it is declared — not even subclasses can access it directly.


class C1 {
  v1 = 1;   // public, accessible everywhere
  #v2;      // private, accessible only within this class

  constructor(value) {
    this.#v2 = value;
  }
}

class C2 extends C1 {
  constructor() {
    super();
    // this.v3 = this.#v2;   // error
  }
}

const o = new C2;
console.log(o.v1);
// console.log(o.#v2);

1 Uncaught SyntaxError: Private field '#v2' must be declared in an enclosing class

Besides private instance fields, ECMAScript 2022 also allows private methods and private accessors (getters/setters), all declared with the # prefix and all only callable from within the class body.


class Counter {
  #count = 0;                                 // private field
  #step() { return 1; }                       // private method
  get #doubled() { return this.#count * 2; }  // private getter

  increment() {
    this.#count += this.#step();
    return this;
  }

  get value() { return this.#doubled; }   // public method exposing private state
}

var c = new Counter();
c.increment().increment();
console.log(c.value);

// c.#step();   // SyntaxError outside the class body

4

To test whether an arbitrary object has a given private field, without risking a TypeError, ECMAScript 2022 also added the 'ergonomic brand check': #field in obj.


class Point {
  #x;
  constructor(x) { this.#x = x; }
  static isPoint(obj) { return #x in obj; }   // brand check, works even if obj is null or unrelated
}

console.log(Point.isPoint(new Point(1)));
console.log(Point.isPoint({x: 1}));
console.log(Point.isPoint(null));

true false false