11

I'm trying out the new class private member feature However, I've quickly run into a problem: How does one dynamically access them?

I expected it to follow pre-existing syntax of either

constructor(prop, val) {
  this[`#${prop}`] = val; // undefined
}

or

constructor(prop, val) {
  this.#[prop] = val; // syntax error
}

However, both of the above fail.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Jakob Jingleheimer
  • 30,952
  • 27
  • 76
  • 126
  • have you tried creating the property first and setting it? – AngelSalazar Apr 13 '20 at 21:54
  • Yes, my actual pre-declares them as empty objects. If I manually enumerate them all in the constructor and set them to the vals, that works fine (albeit very tedious and brittle), but then the catch-all get and set have the same problem—which defeats their purpose, so there must be a way to do this dynamically (otherwise the JS authors made a huge oversight). – Jakob Jingleheimer Apr 13 '20 at 21:57
  • `this[\`#${prop}\`]` would just set a normal property as there would be no way of telling if it was meant as private or a regular legitimate property name containing a `#`. As for `#[prop]` in the [tc39 proposal](https://github.com/tc39/proposal-class-fields/blob/master/README.md#private-syntax) that is said to be a syntax error as there is no computed prop names for them. So a dynamic access may not be available in the current stage – Patrick Evans Apr 13 '20 at 22:02
  • 2
    Ugh, I hope not as that would be a prohibitive/preclusive limitation. The MDN documentation suggests that the `#` is simply part of the name, so based on that, I would expect concatenation to work just fine – Jakob Jingleheimer Apr 13 '20 at 22:04
  • simply you cant! – curiousBoy Apr 13 '20 at 22:04
  • @jacob I guess you would have to implement a module pattern – AngelSalazar Apr 13 '20 at 22:06
  • If you could do this, then you could also access the property from outside the class using the same method. – Barmar Apr 13 '20 at 22:06
  • @Barmar that's not true – Jakob Jingleheimer Apr 13 '20 at 22:07
  • @AngelSalazar my workaround is to have constructor return a `Proxy` – Jakob Jingleheimer Apr 13 '20 at 22:08
  • 2
    @jacob check adriancg answer – AngelSalazar Apr 14 '20 at 15:48

6 Answers6

8

Another option is to have a private object for the keys you want to access dynamically:

class privateTest {
  #pvt = {}

  constructor(privateKey, privateVal) {
    this.#pvt[privateKey] = privateVal;
  }

  getPrivate(privateKey) {
    return this.#pvt[privateKey];
  }

}

const test = new privateTest('hello', 'world');
console.log(test.getPrivate('hello')) // world
adriancg
  • 247
  • 1
  • 1
5

I dont think you can access private fields dynamically. The proposal says:

There are no private computed property names: #foo is a private identifier, and #[foo] is a syntax error.

Barmar
  • 741,623
  • 53
  • 500
  • 612
4

If you reaaaaally wanted to do it.

eval(`this.#${propertyName}`)

But that's just opening a very ugly can of worms.

adriancg
  • 247
  • 1
  • 1
3

From the FAQ on the proposal for private properties:

Dynamic access to private fields is contrary to the notion of 'private'.

https://github.com/tc39/proposal-private-fields/blob/master/FAQ.md#why-doesnt-thisx-access-the-private-field-named-x-given-that-thisx-does

The lack of dynamic access to private fields is by design.

Nels
  • 311
  • 2
  • 6
  • 4
    Though in OP's case (and my case why I'm searching for this), he wants to dynamically access the private field within the private scope. Everything stays private. For me, I have a bunch of variables holding private callback functions and would love to have a string array of method names I can dynamically access. – Phil Tune Mar 18 '21 at 14:40
2

Private fields can not be accessed dynamically. One way to get dynamic private fields is with Symbols.

A symbol is a scalar, just like a string. You create symbols like this:

let myXyzSymbol = Symbol('my_xyz_symbol');
// note the absence of `new`

A symbol is always unique. This means that following is always false:

Symbol('x') === Symbol('x')

Now, we have all the pieces to do something about dynamic privates.

Create a symbol and do not export it outside the module. This makes it private.

const mySym = Symbol('foo');


export class Something {

    [mySym]() {
        // This function can not be called by
        // a caller that does not have access to mySym.
    }

}

More on Symbols

  • The Symbol.for() function is different from Symbol(), but both return a symbol.
  • Check out the MDN docs for Symbols
  • And an article here
treecoder
  • 43,129
  • 22
  • 67
  • 91
  • +1. pretty much the only solution except putting all the privates in a private namespace, as the top-voted answer shows. And thanks for linking to my article. – Mitya Mar 14 '23 at 17:38
1

Private fields and methods can be accessed dynamically, if you have some fantasy!
So, you have two private methods in your class:

  #media(method) {
    return ({
      import: this.#import.bind(this),
    }[method](method))
  }
  
  #import(method) {
    alert(this.#media)
    return {method: '@' + method}
  }

In this case you can call #import like this:

this.#media('import')

Note, you should set the correct scope for #import with bind(this), if you want to use "this" as class pointer inside.

mistercx
  • 21
  • 3