10

How do we enumerate through private class fields?

class Person {
  #isFoo = true;
  #isBar = false;

  constructor(first, last) {
    this.firstName = first;
    this.lastName = last;
  }

  enumerateSelf() {
    console.log(this);
    // (pub/priv fields shown)

    // enumerate through instance fields
    for (let key in this) {
      console.log(key)
      // (only public fields shown)
    }

    // How do we enumerate/loop through private fields too?
  }
}

new Person('J', 'Doe').enumerateSelf();
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
anthumchris
  • 8,245
  • 2
  • 28
  • 53
  • I presume using `Object.getOwnPropertyDescriptors` will help but I've not played around with private properties much. Last time I looked at them, they weren't finalised. – VLAZ Oct 20 '19 at 17:23
  • 1
    By definition, private fields are not accessible outside of the instance to static method calls like `Object.*` – anthumchris Oct 20 '19 at 17:39

2 Answers2

14

It's not possible. They're private fields, and there is no enumeration method for them. Only the class declaration statically knows which ones were declared. They're not properties, there's not even a language value representing a private name, you cannot access them dynamically (like with bracket notation).

The best you'll get is

enumerateSelf() {
    console.log(this);
    for (let key in this) {
        console.log("public", key, this[key]);
    }
    console.log("private isFoo", this.#isFoo);
    console.log("private isBar", this.#isBar);
}

There was an open issue in the private-fields proposal about "Private field iteration", however one of the first comments by a TC39 member states "Private fields are not properties. You can't reflect on them by design.".

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I guess you might also have another private field that lists all the other private fields, so you can cycle through them. But it's going to be annoying, as you need to keep it up to date manually – VLAZ Oct 20 '19 at 18:22
  • @VLAZ You cannot even make a list of field names, as there is no dynamic access to private fields. The best you could do was keeping a list of getters… – Bergi Oct 20 '19 at 18:23
  • 4
    hopefully only temporarily, let me review the ECMA spec and possibly suggest a change. – anthumchris Oct 20 '19 at 18:23
  • 1
    @AnthumChris This unlikely will change. What do you need this for? – Bergi Oct 20 '19 at 18:24
  • @Bergi I was thinking more along the lines of array of strings, so you just do `for(let private of privateFields) this[private]` – VLAZ Oct 20 '19 at 18:24
  • 1
    @VLAZ But private fields are not properties, they cannot be accessed with strings and bracket notation. – Bergi Oct 20 '19 at 18:25
  • @Bergi ah, gotcha now. I wasn't actually aware of that. So...it seems there really is no other way than just listing all in the form of `this.#foo`, `this.#bar`, etc. – VLAZ Oct 20 '19 at 18:26
  • 1
    @VLAZ ([Looks like](https://github.com/tc39/proposal-class-fields/issues/96) it's possible to dynamically access them by use of `eval`. Not suggesting this would be a good solution though :P) – Bergi Oct 20 '19 at 18:34
  • 1
    @Bergi Thanks! I didn't find [#94](https://github.com/tc39/proposal-private-fields/issues/94) when searching. This would be for reflection operations, which #94 describes as not possible by design. – anthumchris Oct 20 '19 at 18:53
  • @AnthumChris I'm not sure any more how I found it either - it's in one of the old repositories… – Bergi Oct 20 '19 at 18:59
4

Maybe not an elegant solution but perhaps you can modify your structure to do something like:

class Person {
  #properties = {
      isFoo: true,
      isBar: false
  };

  constructor(first, last) {
    this.firstName = first;
    this.lastName = last;
  }

  enumeratePrivateSelf() {
    // enumerate through private fields
    for (let key in this.#properties) {
      console.log(key)
      // (only public fields shown)
    }
  }
}