Can I set a private class field using a variable as identifier? How?

Viewed 337

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?

1 Answers

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);

Related