!Re: proposal: Object Members

Darien Valentine valentinium at gmail.com
Sat Jul 28 16:24:30 UTC 2018

Ranando, I share your reservations about private fields being bound too
tightly to class syntax. In my case it isn’t because I don’t want to use
classes, but rather because in the last few years, using the WeakMap
solution, a good number of times I’ve needed to do things which the private
field proposal either doesn’t permit or account for:

- Adding the same “slot” to multiple classes which don’t inherit from each
- Selectively sharing access to private state through functions declared
outside the class body
- Adding slots dynamically, e.g. when adding mix-in methods that may
initialize a new slot if necessary when called, since subclassing is not
always appropriate

With the WeakMap solution, the privacy mechanism is one that already
exists: a scope. This makes it very flexible (it handles the above three
cases fine), but it has a key limitation in terms of achieving privacy,
which is that `global.WeakMap` and `WeakMap.prototype` may be compromised.
Given this limitation — plus the amount of boilerplate WeakMap privacy can
entail — I am very happy to see private instance state being addressed
syntactically. However because the model chosen for “scope of privacy” is
“class declaration body” — not previously something that provided a
closure/scope at all? — instead of just using existing scopes, I have found
them impractical to use in some cases.

If I’m understanding your alternative proposal, Ranando, I don’t think it
addresses these issues either, not in the way I’m looking for anyway — I’m
wishing for a syntactic solution for true private slots on objects, but
where said slots are associated with a scope (almost always a module scope)
rather than a class declaration. In particular, I’m not convinced that the
concept of “protected” makes sense within the JS models of objects and

I’m gonna get more detailed about what I see as inadequacies in the current
proposal. These are subjective, but not hypothetical: I’ve been doing
WeakMap-based privacy for a few years now and I’ve tried converting
existing code to use private fields since Chrome shipped it behind a flag.
I found that, unfortunately, it did not meet my needs.


Regarding exposing functions that operate on private state but which do not
live on the constructor or prototype — there is a way to achieve this in
the proposed spec. It’s awkward, but it is technically possible:

class Foo {
  #bar = 1;

  getBarOfFoo(foo) {
    return this.#bar;

  // [[ ... other methods that may manipulate but do not expose #bar here
... ]]

const { getBarOfFoo } = Foo.prototype;
delete Foo.prototype.getBarOfFoo;

It gets more awkward in the “multiple classes with the same semantic slot”
case, since one will have to wrap each attempted access in a try-catch, as
there is no other way to be certain whether the target has the slot. With
WeakMap, in contrast, one will just get undefined — and one may use the
same WeakMap to manage the same slot across multiple classes that are
declared in the same scope as the WeakMap.

Assume we have two classes with a private bar “slot” which is meant to be
semantically equivalent. It holds an integer. We want to create a function
that adds together two bar values from any classes that implement this
slot. If an argument has no bar slot, bar defaults to zero. With WeakMaps,
such a function might look like this:

function addBars(a, b) {
  return (wm.get(a).bar || 0) + (wm.get(b).bar || 0);

Realizing the same logic with classes that use private field syntax is
still possible (using the aforementioned “pop off a method” pattern), but
now it looks like this:

function addBars(a, b) {
  let aBar, bBar;

  try {
    aBar = getBarOfFoo(a);
  } catch {
    try {
      aBar = getBarOfBaz(a);
    } catch {
      aBar = 0;

  try {
    bBar = getBarOfFoo(b);
  } catch {
    try {
      bBar = getBarOfBaz(b);
    } catch {
      bBar = 0;

  return aBar + bBar;



This is a more minor issue, but assuming we *can’t* have dynamic slots, I
would like to take advantage of the fact that
whether-a-function-may-access-a-slot is statically knowable by having
immediate brand checking occur in all methods that may access private
state. This is actually the main source of boilerplate in the WeakMap
solution (for me, but admittedly I’m probably in a tiny minority here):

set foo(value) {
  if (!wm.has(this)) throw new TypeError(`Illegal invocation`);

  const str = String(value);

  if (VALID_FOO_VALUES.has(str)) {
    wm.get(this).foo = str;
  } else {
    throw new Error(`Invalid value for foo`);

The difference between the above function with and without the guard
concerns guarantees about behavior. The `String(value)` call actually might
throw, but it ought to be predictable that a method which requires a
branded receiver always throws the same error when called on anything
unbranded — even if (especially if!) private state access occurs in the
method only conditionally, since throwing/not-throwing/what-gets-thrown
makes an implementation detail observable. The above example is minimal,
but there could be more involved state manipulation or observable effects
that occur prior to the first private access, possibly leading to being
left in an invalid state.

Note that all host and intrinsic functions that may access slots perform
these checks. It is the existing pattern in the language for this, and with
a syntactic solution, it could be enforced automatically. Right now, with
the existing proposal, the boilerplate still exists:

set foo(value) {
  try {
  } catch {
    throw new TypeError(`Illegal invocation`);

  const str = String(value);

  if (VALID_FOO_VALUES.has(str)) {
    this.#foo = str;
  } else {
    throw new Error(`Invalid value for foo`);

(You could drop the try-catch if you don’t care whether the error thrown
reveals implementation details, but if, like me, you are aiming for
behavior matching host APIs, the boilerplate actually increases.)

I can understand if these early checks are deemed undesirable, because they
are strictly less flexible than the current proposed behavior, and they
would also be incompatible with any solution that allows slots to be added
dynamically (unlike the current proposal). However between this and the
inability to manage privacy by scope instead of by class declaration body,
I will probably find myself sticking with WeakMaps in general (in library
code, anyway) because my attempted conversions have often increased rather
than reduced complexity and verbosity.


Sorry this is a long post. It’s hard to talk about this subject without
getting pretty wordy, but hopefully this is useful feedback about what at
least one dev is looking for with private slots. It seems, admittedly, that
those of us who need private slots to remain "reflectable" are in the

FWIW I actually love `#` syntax though :)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180728/a8580ecb/attachment.html>

More information about the es-discuss mailing list