3

Node.js 12 supports private class fields denoted by # out-of-the-box, without flags or transpilers.

For example, this works with Node.js 12:

class Foo {
  #bar = 1;

  constructor({ bar }) {
    this.#bar = bar;
  }

  get bar() {
    return this.#bar;
  }
}

const foo = new Foo({ bar: 2 });

console.log(foo.bar); // 2

Let's say I want to construct my Foo instance not with 1 property, but with 20 – I would have to duplicate the assignment statement in the constructor and the getter function 20 times, which makes for a lot of boilerplate code.

If I didn't use private fields but regular class fields, this would not be hard to avoid:

class Foo {
  bar = 1;

  constructor(properties) {
    Object.entries(properties).forEach(([name, value]) => (this[name] = value));
  }

  get bar() {
    return this.bar;
  }
}

const foo = new Foo({ bar: 2 });

console.log(foo.bar); // 2

However, with private class fields, it does not work:

class Foo {
  #bar = 1;

  constructor(properties) {
    Object.entries(properties).forEach(
      ([name, value]) => (this[`#${name}`] = value)
    );
  }

  get bar() {
    return this.#bar;
  }
}

const foo = new Foo({ bar: 2 });

console.log(foo.bar); // 1 :-(

I've also tried assigning a value to the private class field in the constructor using Reflect.set, to no avail:

class Foo {
  #bar = 1;

  constructor(properties) {
    Object.entries(properties).forEach(([name, value]) =>
      Reflect.set(this, `#${name}`, value)
    );
  }

  get bar() {
    return this.#bar;
  }
}

const foo = new Foo({ bar: 2 });

console.log(foo.bar); // 1 :-(

Can I set a private class field using a variable as identifier? If yes, how?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Patrick Hund
  • 19,163
  • 11
  • 66
  • 95
  • For future reference, when you're asking yourself how to declare 20 variables without duplicating code 20 times, you're typically just wanting some sort of collection such as an array or dictionary (e.g. object or map), as suggested below. – Patrick Roberts Oct 09 '19 at 04:50
  • @PatrickRoberts I'm experimenting with the private class fields as a way to create immutable objects, is there an elegant way to achieve this with objects or maps? – Patrick Hund Oct 09 '19 at 04:59
  • 1
    I would be inclined to say no. That's exactly what [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) is for, though. – Patrick Roberts Oct 09 '19 at 05:02

1 Answers1

4

No, this doesn't look to be possible. From the proposal FAQ:

Why doesn't this['#x'] access the private field named #x, given that this.#x does?

  1. This would complicate property access semantics.

  2. Dynamic access to private fields is contrary to the notion of 'private'. E.g. this is concerning:

class Dict extends null {
  #data = something_secret;
  add(key, value) {
    this[key] = value;
  }
  get(key) {
    return this[key];
  }
}
(new Dict).get('#data'); // returns something_secret

The syntax is such that every private field must be initialized and/or referenced with # before the literal property name, and nothing else. Not even bracket notation is permitted.

Having a private field named x must not prevent there from being a public field named x, so accessing a private field can't just be a normal lookup.

You can't even reference a private field unless it's explicitly defined in the class body (not a class function, but inside the class body directly):

class Foo {
  // error is thrown because #abc must be defined in this section
  doSomething() {
    return this.#abc;
  }
}

That said, nothing's stopping you from creating a private property which is an object, with all of those properties on the object:

class Foo {
  #privates = {};

  constructor(properties) {
    Object.entries(properties).forEach(
      ([name, value]) => (this.#privates[name] = value)
    );
  }

  get privates() {
    return this.#privates;
  }
}

const foo = new Foo({ bar: 2 });

console.log(foo.privates.bar);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320