How to Enumerate Private JavaScript Class Fields
Asked Answered
B

2

12

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();
Bearing answered 20/10, 2019 at 17:21 Comment(2)
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.Habanera
By definition, private fields are not accessible outside of the instance to static method calls like Object.*Bearing
L
16

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.".

Leathery answered 20/10, 2019 at 18:12 Comment(10)
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 manuallyHabanera
@Habanera 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…Leathery
hopefully only temporarily, let me review the ECMA spec and possibly suggest a change.Bearing
@AnthumChris This unlikely will change. What do you need this for?Leathery
@Leathery I was thinking more along the lines of array of strings, so you just do for(let private of privateFields) this[private]Habanera
@Habanera But private fields are not properties, they cannot be accessed with strings and bracket notation.Leathery
@Leathery 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.Habanera
@Habanera (Looks like it's possible to dynamically access them by use of eval. Not suggesting this would be a good solution though :P)Leathery
@Leathery Thanks! I didn't find #94 when searching. This would be for reflection operations, which #94 describes as not possible by design.Bearing
@AnthumChris I'm not sure any more how I found it either - it's in one of the old repositories…Leathery
S
3

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)
    }
  }
}
Sidetrack answered 18/2, 2021 at 21:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.