# JS Class fields potentially harmful

**URL:** <https://es.discourse.group/t/js-class-fields-potentially-harmful/1616>\
**Category:** Spec Reading\
**Tags:** proposal\
**Created:** [February 14, 2023, 7:42pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616 "2023-02-14T19:42:55Z")\
**Posts on this page:** 20\
**Page:** 3

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [February 18, 2023, 4:11pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/41 "2023-02-18T16:11:03Z")

</div>

Whether it’s good or not isn’t particularly relevant; it’s a largely unavoidable consequence of the ES3 (probably earlier) ability to return arbitrary objects from constructors, and the ES6 decision to preserve these semantics in `class` syntax.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 18, 2023, 4:14pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/42 "2023-02-18T16:14:09Z")

</div>

let me try again, demoing leaks of private fields to a foreign object:

```javascript
class Parent {
  constructor() {
    return {}; // foreign object
  }
}

class Sub extends Parent {
  #leak = 'secret';
  constructor() {
    // developer intent to bind the click method
    const {onclick} = Sub.prototype;
    // oooops, super() is not an instance of this class
    super().onclick = onclick.bind(this);
  }
  onclick() {
    // expose the leak in any undesired way
    console.log(this.#leak);
  }
}

(new Sub) instanceof Sub; // false
(new Sub) instanceof Parent; // false
(new Sub).onclick();
// logs the leaked private from Sub

```

does anyone believe this is a good part of the fields instantiated no-matter-what _after_ the `super` call is acceptable by any OOP meaning?

> [@ljharb](#):
>
> the ES6 decision to preserve these semantics in `class` syntax.

we are in stage 4 ... nothing is written in stones ... can we see the current approach that mimic C++ and Java but not Python or PHP and the fact classes fields are defined _after_ because of ES3 is problematic?

So far every argument against my proposal brought more issues to the plate to me ... C++ and Java developers likely won't even ever use classes in JS (or even think about returning something else is even allowed) while people coming from other scripting PLs will have to learn the hard way what they write in classes as fields is not always what they get/expect ... there's no control by any mean to how these fields behave (decorators can't prevent issues from happening) and there are leaks ... the resolution looks like: "_meh_"?

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [February 18, 2023, 4:26pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/43 "2023-02-18T16:26:33Z")

</div>

There’s no leak in that example - Sub is either intentionally exposing a private field’s value/presence, or isn’t, and Parent is merely deciding the this object for the Sub constructor (and choosing to extend something is ceding a certainty amount of control/trust to it implicitly).

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 18, 2023, 5:55pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/44 "2023-02-18T17:55:23Z")

</div>

We can ignore the issue as much as we like but the returned foreign object has nothing to do with the class and its prototype:

- any method would fail
- any accessor would fail
- any brand check would fail
- it gets **potentially accidentally** fields attached and from that point on, it can **accidentally get methods that expose fields too** (please don't focus on my explicit example, think about any _smart_ library helper or even _decorator_ out there that accidentally can leak everything to foreign objects!)

There's no equivalent in either C++ or Java or Python or PHP of this potentially accidental situation, which represents all issues that in the past needed to get fixed with way too much time in between releases as specification and fixes at distance, namely:

- the `undefined` potential leak via `window[shenaniganNobodyKnows] = value`
- the ` __proto__ ` field that cursed all platforms, all runtime, and there's a recent stage something proposal to fix that

In all that discussions (especially the latter one where I was the first opponent to ` __proto__ `) I've lived already what's happening here: denial, "_too late_", mentions around the fact code would break at this **stage 4** time without any evidence, and so on ... so I am concerned: are we learning from the past that when an issue is raised at **any stage version** should be considered, instead of just diminishing it?

I am just trying to avoid amends and fixes with `"use intended classes fields"` directive in 5+ years (and all discussions, effort, and _really_ too late work, or fixes in production, that would require) 🤷

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 18, 2023, 5:59pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/45 "2023-02-18T17:59:02Z")

</div>

maybe I should mention in my last 5 years I worked on JS that catches all these potential exploits that evil code **will use** in the wild as soon as a not-so-tested conditional return in any important constructor can get exploited ... and the "_who cares_" here is overly-concerning to me.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [February 18, 2023, 8:58pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/46 "2023-02-18T20:58:36Z")

</div>

You have to go out of your way to make return override work at all, and at that point you're far from OOP anyway. Thus I don't consider stamping a private fields onto a foreign object a class encapsulation problem.

That said, I very much dislike the ability to stamp private fields onto random objects as it effectively creates a syntax-based undeniable `WeakMap`. For the same reason the [DOM wants to exempt some of its objects](https://github.com/whatwg/html/issues/7952) from this, I want the ability to exempt some objects similarly. That said it's out of topic for this thread.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 18, 2023, 9:10pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/47 "2023-02-18T21:10:51Z")

</div>

> [@mhofman](#):
>
> You have to go out of your way to make return override work at all

it's [the only way to extend functions](https://github.com/WebReflection/custom-function#readme) without needing CSP privileges due evaluation, [details here](https://github.com/WebReflection/custom-function/blob/d84c0ae551b904d5f6b3f36d10b4db53419f695d/esm/index.js), it's also the only way [to extend DOM builtins](https://github.com/WebReflection/nonchalance#readme) without the registry, but it's also the easiest way to _observe_ and provide observability by overriding a return constructor with a _Proxy_ ... and I feel like I've just scratched the surface of reasons people might want to do that, which is more common than people in here might think (apparently).

> [@mhofman](#):
>
> at that point you're far from OOP anyway

not really, see any of my linked examples and think about returned _Proxy_ for "_magic instances behavior_" inherited from super classes ... JS allows that, and it's demonstrated to work, faster than usual alternative to augment the instance too!

The evil is once again in the details: found an exploit to a conditional return that can be compromised via some `Object.prototype` overload in the evil wild code, or replacing `Proxy` from the `globalThis` or `window` many care to reach for globals, found a security breach for either front-end or back-end related cases ... I am specially thinking about databases abstractions and possible conditional intended overloads that might leak due intended implicit bound methods attached after, as well as any Custom Element that due polyfills or indirections might as well result compromised ... the fact any method accessing a private field can be bound to anything and expose such field if somehow hijacked through a parent constructor horrifies me ... which is why I am insisting the issue as a whole I've raised here spans from developers expectations to security concerns and "_it's OK_" or "_it's too late_" (on a stage proposal) are in my opinion not answers at all.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [February 18, 2023, 10:25pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/48 "2023-02-18T22:25:58Z")

</div>

> [@WebReflection](#):
>
> on a stage proposal

Class fields are not a proposal they have been standardised since ES2022.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 18, 2023, 11:01pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/49 "2023-02-18T23:01:33Z")

</div>

OK, if nothing can be done then, I rest my case ... I still wonder what's the point of having a stage 4 if nothing that raises, when people use stuff landed, can be ever changed 🤷

I read specs from ECMAScript standard, not out of stage 4 list, it'd be great to have this stage step more clarified ... landed on browsers can be experimental too, landed definitively and immutable makes it a standard, not a stage 4 to me.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [February 18, 2023, 11:05pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/50 "2023-02-18T23:05:46Z")

</div>

Others who have been involved with the committee for longer may have a better description of stage 4. But I see it as the period of time when a proposal is "finished" and will be accepted into the latest draft spec but as EcmaScript gets a new version each year there is a delay before it is in an actual "version" of the language. The current stage 4 proposals for example are currently waiting for the ES2023 "cut".

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 18, 2023, 11:08pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/51 "2023-02-18T23:08:23Z")

</div>

so ... is this ES2023 already or can it be anyhow improved, after rising concerns? if not, I'll keep advocating around the topic helping developers avoid footguns ... if there's a tiny chance, I'll be happy to keep answering questions and provide more examples around issues.

---

<div class="post-metadata">

**Author:** ![bakkot](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bakkot/32/22_2.png) [@bakkot](https://es.discourse.group/u/bakkot)\
**Post date:** [February 18, 2023, 11:24pm UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/52 "2023-02-18T23:24:20Z")

</div>

The important part of whether something can change is less about what stage it is and more about whether people have started shipping code which depends on it. And people have absolutely started shipping code which depends on class fields, including details like this. It is extremely unlikely to change at this point even if everyone was convinced that it would be better if it had been otherwise (and at least some people, including myself, are not convinced).

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [February 19, 2023, 12:15am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/53 "2023-02-19T00:15:18Z")

</div>

Stage 3 is when implementation starts, and no major changes to the semantics are expected, but the proposal can and is updated based on implementer and user feedback. Arguably, stamping private fields on foreign objects could have been a change considered at stage 3, but the overall [class fields proposal reached stage 4 almost 2 years ago](https://github.com/tc39/notes/blob/bee79ce70548650c568155d750167d9da1644bf5/meetings/2021-04/apr-19.md#class-fields-private-methods-and-static-class-features-for-stage-4), so it's definitely too late for such a change.

FWIW, that proposal was at [stage 3 for a whole 10 years](https://github.com/tc39/proposals/commit/38b79d0b0e71df6f9ebfdd6c25c915e1f9e467d7), and the specific return override behavior was definitively discussed plenty of times over the years. I wasn't around then, and I don't know if creating an undeniable WeakMap through pure syntax was something that was fully considered, but I've accepted that this behavior won't change wholesale, and I'll need to find and champion a more specific carveout for my concern.

I still do not sufficiently understand your concerns with this behavior to know if they were fully considered. Instead of requesting a full change of the return override and private fields behavior, I would suggest you try to identify a subset of cases where the behavior is problematic, and try to come up with a potential idea of how it could be mitigated.

One solution I've considered is some kind of API to "private seal" an object and prevent new private fields from being added. However, I do not want this to look like `Object.seal` where this ability can be applied on objects I did not create. One possibility may be to add a new `isPrivateExtensible()` trap on proxies, and thus restrict the stamping opt-out to proxy or other host exotic objects.

---

<div class="post-metadata">

**Author:** ![bergus](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bergus/32/152_2.png) [@bergus](https://es.discourse.group/u/bergus)\
**Post date:** [February 19, 2023, 12:39am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/54 "2023-02-19T00:39:44Z")

</div>

> [@ljharb](#):
>
> it’s a largely unavoidable consequence of the ES3 (probably earlier) ability to return arbitrary objects from constructors, and the ES6 decision to preserve these semantics in `class` syntax.

Well there was no reason to preserve those semantics for private fields. I had always expected private fields to work exactly like ECMAScript internal slots, that are being implicitly passed in the `super()` call to allow allocation when the object is created.  
Also putting private methods on each instance, not on the prototype, breaks established semantics as well.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [February 19, 2023, 1:42am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/55 "2023-02-19T01:42:31Z")

</div>

They do work exactly like internal slots - the object is created (by the super call) and then the slots are installed.

---

<div class="post-metadata">

**Author:** ![bergus](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bergus/32/152_2.png) [@bergus](https://es.discourse.group/u/bergus)\
**Post date:** [February 19, 2023, 2:14am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/56 "2023-02-19T02:14:15Z")

</div>

Uh, really? From my understanding, the slot list is compiled before, and passed as an argument to [_MakeBasicObject_](https://262.ecma-international.org/13.0/#sec-makebasicobject) (often through [_OrdinaryObjectCreate_](https://262.ecma-international.org/13.0/#sec-ordinaryobjectcreate)).

[ECMAScript® 2022 Language&nbsp;Specification](https://262.ecma-international.org/13.0/#sec-object-internal-methods-and-internal-slots) says

> […] internal slots are allocated as part of the process of creating an object and may not be dynamically added to an object. […] the ECMAScript language provides no direct way to associate internal slots with an object.

Admittedly, in the omitted part of the quote it says "_Unless explicitly specified otherwise_", but I have not found any example of that. So my mental model always was that the set of internal slots of an object is immutable and known when the object is created. This is different from private fields.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 19, 2023, 7:14am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/57 "2023-02-19T07:14:26Z")

</div>

> [@bergus](#):
>
> Also putting private methods on each instance, not on the prototype, breaks established semantics as well.

This **is** indeed what this thread is about, both the fact the created object behind the scene _could_ have already attached fields when _super_ happens and the fact semantics at class definitions are different only for fields and not retrieved from the class ... it's like an utility a part that is invisible and called without any way to prevent it around the _super()_ ... as in ` __polluteRegardless__ (super(...args))` ... if decorators _could_ allow a way to prevent this or a way to handle fields from the class the issue and mental model _could_ be fixed (on user-land, at least).

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [February 19, 2023, 7:19am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/58 "2023-02-19T07:19:18Z")

</div>

> [@mhofman](#):
>
> that proposal was at [stage 3 for a whole 10 years](https://github.com/tc39/proposals/commit/38b79d0b0e71df6f9ebfdd6c25c915e1f9e467d7),

TypeScript 5 is beta and it's the first time that it adopts decorators as meant by specs (just to argue that having stages for years is not always meaningful for users) + I've pinged Nicolo' around Babel transformer keeping latest decorators proposal at Stage 2 level, and it was breaking at 3 or 4 so while I am sure people already use classes fields, I wonder if they are exploiting the current result on purpose or they just used fields for properties that never require an overload.

I find it extremely difficult to justify foreign objects being polluted with stuff that doesn't belong to them so I'd be very interested in learning _when_ this is meant, needed, or desired, 'cause I cannot think about a single use case where that's the case ... I rather would expect surprises for non tested code.

Few developers (linked at the beginning of this thread) already got bitten and decided to move away from fields ... they use fields only for primitives and not for primitives that need overrides.

Can anyone provide a single use case where the current spec-behavior is intended, useful, or desired?

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [February 19, 2023, 10:11am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/59 "2023-02-19T10:11:46Z")

</div>

The talks on the return override installing private fields was also before my time, but I feel like someone once told me one reason was web CustomElements when the element gets upgraded. Maybe others could expand/correct me.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [February 19, 2023, 10:20am UTC](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616/60 "2023-02-19T10:20:53Z")

</div>

Searching in the notes archive this looks like it might be of interest:

> <https://github.com/tc39/notes/blob/bee79ce70548650c568155d750167d9da1644bf5/meetings/2019-01/jan-31.md?plain=1#L81>

[Previous page](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616.md?page=2)

[Next page](https://es.discourse.group/t/js-class-fields-potentially-harmful/1616.md?page=4)
