# Protected support for \`class\`

**URL:** <https://es.discourse.group/t/protected-support-for-class/68>\
**Category:** 💡 Ideas\
**Created:** [May 28, 2019, 4:33pm UTC](https://es.discourse.group/t/protected-support-for-class/68 "2019-05-28T16:33:59Z")\
**Posts on this page:** 13\
**Page:** 2

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [December 22, 2019, 4:22am UTC](https://es.discourse.group/t/protected-support-for-class/68/21 "2019-12-22T04:22:21Z")

</div>

> [@trusktr](#):
>
> Why is that the rule? Is that your preference? Why do you prefer that?

It's not a preference. It's the rule that exists in compiled class-based languages. Consider this. Suppose "A" was "Shape", "B" was "Box", "C" was "Circle", and "A::p" was the abstract function "RenderShape". If "B" could call "C::p", then the functions in B expecting the data of a quadrilateral would instead receive data for a circle. Bad deal. Sure the example is contrived, but the reality stays the same regardless.

Another way to think of it is that a "protected" member is a private member shared specifically with a selected descendant. The shared element is therefore "effectively private" to that descendant as well as sharable to further generations. So despite the common ancestor, the shared protected member is private with respect to the inheriting class. There should be no means for another class to see that data despite inheriting the same members from the ancestor.

> [@trusktr](#):
>
> Any of the rules we've discussed are implementable. A more important thing may be to get a native implementation working (with any rules resembling protected) and then go from there.

I'd love to see this become natively implemented. I'm fairly certain a lot of other developers would as well.

---

<div class="post-metadata">

**Author:** ![trusktr](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/trusktr/32/183_2.png) [@trusktr](https://es.discourse.group/u/trusktr)\
**Post date:** [December 22, 2019, 10:06am UTC](https://es.discourse.group/t/protected-support-for-class/68/22 "2019-12-22T10:06:55Z")

</div>

> [@rdking](#):
>
> then the functions in B expecting the data of a quadrilateral would instead receive data for a circle. Bad deal

Makes sense, that could cause a problem. I agree, this may be better. This case is covered by either of my two options anyways.

I think it'd be interesting to have the ability to specify differing levels of protectedness somehow, which would allow communication across sibling classes, but still keep things hidden from public. Do any languages have that?

> [@rdking](#):
>
> I'd love to see this become natively implemented. I'm fairly certain a lot of other developers would as well.

It's making me want to get the Firefox source and give it a try.

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 12, 2020, 5:27am UTC](https://es.discourse.group/t/protected-support-for-class/68/23 "2020-07-12T05:27:41Z")

</div>

I know I'm way late in replying, but I've been thinking about @ljharb and his objections toward "protected" being added. I'll get to that in just sec.

> [@trusktr](#):
>
> I think it'd be interesting to have the ability to specify differing levels of protectedness somehow, which would allow communication across sibling classes, but still keep things hidden from public. Do any languages have that?

That's precisely the standard approach to the **`class`** keyword used by most compiled languages.

- **private:** accessible only to member functions of the declared class.
- **protected:** accessible to member functions of the declared and descendant classes.
- **public:** accessible to any function via the instance object.

In more recent languages, there's also something similar to...

- **internal:** accessible to all functions within the same compiled module via the instance object.

* * *

@ljharb

Over the years that I've known your stance on this issue, you've been fond of saying something like this:

> [@ljharb](#):
>
> JS only has two kinds of "visibility": reachable and unreachable. The word "public" and "private" may suggest that it's the same as "access levels", but it's not.

That's a direct quote, but you've said something similar many times. My question to you is whether or not that stance is unassailable. As you know, I believe it to be flawed with as strong a conviction as you believe it to be sound. I now have a potential way of showing you that flaw. Hopefully you'll humor me and discuss this. I'll begin here:

Your statement above implies that the "visibility" in ES is not reasonably similar to "access levels" provided by compiled languages (and other languages supporting both "class" and "protected". I find fault for 3 main reasons:

1. While ES does indeed only support 2 levels of visibility, those visibility levels are specific to a function environment. Put simply, just because a function has access to certain data available via a certain object during a certain run of that function, does not mean that on subsequent runs of that function, the same access will be afforded. It also does not imply that other functions declared within the same scope will have that same access. This implies that the visibility you mention is already conditional. It's not simply a matter of whether something is visible or not from this point in the code, but rather whether or not it is visible from this point in the code on this run with these parameters and this environment configuration. The existence of conditional visibility is the core feature required to create a "protected" visibility.
2. The existence of a "protected" visibility does not change the truth that given the constraints mentioned above, a member of an object will either be accessible or not. The simple fact that "protected" can be emulated in a secure fashion that would even meet with your requirements (should you allow for such thing) is by itself proof that the language can support such a concept without breaking the existing all-or-nothing visibility constraint of ES.
3. The concept of "accessibility levels" (as I desire it implemented, at least) is not at all incompatible with "visibility", but rather a recognition that "visibility" is already conditional in ES. Taking advantage of that conditional nature is the core method for producing the "protected" functionality desired.

You mentioned before that such is not seen often in the wild. That's true. It's likely you've almost never seen it, but that is also for good reasons.

- The abstraction is complicated to implement properly in ES.
- There is nothing even remotely ergonomic about such an implementation.
- Most developers do most of their development work for a company that is not likely to approve non-standard, not well-known, not major company backed libraries, even if they provide a reasonable benefit to the productivity of a developer.

There's more reasons, but those are the top 3 that I can think of. Despite this, you can already see from the traffic in the GitHub repo that there is a desire to also have "protected" visibility within **`class`**. The absence of this feature constrains the usability of **`class`** to the point that the arguments against it made by those who don't see it's utility are indeed correct. There is scarce little that provides any unique benefit to using **`class`**. Compound this with the array of footguns packed into the current "private fields" proposal and the unfortunate reality is that **`class`** does not provide the power and flexibility required by those who would otherwise take full advantage of it.

---

<div class="post-metadata">

**Author:** ![CrazyPython](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/crazypython/32/422_2.png) [@CrazyPython](https://es.discourse.group/u/CrazyPython)\
**Post date:** [July 22, 2020, 3:33pm UTC](https://es.discourse.group/t/protected-support-for-class/68/24 "2020-07-22T15:33:32Z")

</div>

~~JavaScript already supports this feature:~~

~~

```javascript
class MyClass {
    get field() { return undefined; }
    constructor(value) {
       super.field = value;
    }
    method() {
       super.field *= 5;
    }
    log() {
        // still accessible from within the class
        console.log(super.field);
    }
}

```

~~

~~(Unfortunately, V8 optimizes this pattern poorly.)~~

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [July 22, 2020, 6:03pm UTC](https://es.discourse.group/t/protected-support-for-class/68/25 "2020-07-22T18:03:39Z")

</div>

> [@CrazyPython](#):
>
> JavaScript already supports this feature:

I wish. Your `super.field` exists on the prototype. You just modified the prototype of all instances of `MyClass`. That is by no means the same as what `protected` grants you.

---

<div class="post-metadata">

**Author:** ![ArthurLobopro](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/arthurlobopro/32/1616_2.png) [@ArthurLobopro](https://es.discourse.group/u/ArthurLobopro)\
**Post date:** [January 15, 2023, 10:48pm UTC](https://es.discourse.group/t/protected-support-for-class/68/26 "2023-01-15T22:48:46Z")

</div>

Hi, the ECMAScript 2022 brought support for `private` methods and properties using `#`. But still without protected support. I think that now is a good moment to implement it.

---

<div class="post-metadata">

**Author:** ![bkatzung](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bkatzung/32/2721_2.png) [@bkatzung](https://es.discourse.group/u/bkatzung)\
**Post date:** [August 30, 2026, 11:31pm UTC](https://es.discourse.group/t/protected-support-for-class/68/27 "2026-08-30T23:31:36Z")

</div>

It's possible to simulate protected properties and methods pretty efficiently by building on private elements.

---

<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:** [August 31, 2026, 4:30am UTC](https://es.discourse.group/t/protected-support-for-class/68/28 "2026-08-31T04:30:25Z")

</div>

That's because the public/protected/private triple in many languages represents an "access level" system, which JS simply does not have. In JS, things are either public/reachable, or private/unreachable, with no middle state. Private fields are the same as closed-over variables inside a function body - private/unreachable, even via reflection. "Protected" simply does not make sense in this language - wanting it is simply a category error.

---

<div class="post-metadata">

**Author:** ![bkatzung](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bkatzung/32/2721_2.png) [@bkatzung](https://es.discourse.group/u/bkatzung)\
**Post date:** [August 31, 2026, 2:35pm UTC](https://es.discourse.group/t/protected-support-for-class/68/29 "2026-08-31T14:35:09Z")

</div>

I'm using the following definitions:

Public: accessible from any code

Private: accessible only from code within the declaring class (#)

Protected: accessible only from code within the classes associated with a particular instance

Insider/"friend": accessible only from code within an enumerated list of trusted classes

By protected, I mean that given:

`class A { }`  
`class B extends A {}`  
`class C extends B {}`  
`class D extends A {}`  
`const a = new A(), b = new B(), c = new C(), d = new D();`

then class A methods can see the protected state of instances a, b, c, and d; class B methods can see the protected state of b and c (but not a or d); class C methods can see the protected state of c (but not a, b, or d); and class D methods can see the protected state of d (but not a, b, or c).

By insider/"friend", I mean that if class A trusts class B, then the insider state is visible to the methods of class A and class B, and no other classes or code.

I have ES2022 (#-based) implementations for both the [protected](https://github.com/bkatzung/protected-js) and [insider](https://github.com/bkatzung/insider-js) patterns. I can also link to my walk-through videos on YouTube if you're interested.

---

<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:** [August 31, 2026, 4:46pm UTC](https://es.discourse.group/t/protected-support-for-class/68/30 "2026-08-31T16:46:08Z")

</div>

Those definitions are precise, and that's genuinely useful here - because it makes clear which half of the protected one is implementable.

> Protected: accessible only from code within the classes associated with a particular instance

Split that in two:

- **accessible from code within those classes** - implementable, and your pattern does it.
- **only** - not implementable. And that word is the entire content of "protected". Without it, what you've built is sharing, not an access level.

Here is the published `protected-js` pattern, unmodified, with the exact `B`/`C` hierarchy from its own `demo.js`. The attacker is ordinary outside code holding a constructed instance:

```js
// 2 lines. `static __protected` is a public, writable object which is the
// prototype of every instance's protected-state object.
let s;
victim.constructor.__protected.logState = function () { s = this; };
victim.callProtectedLogger(); // the library itself then calls this.#_.logState()

s.base; s.propB; s.propC; // read protected state the outsider never wrote
s.__this === victim; // true — and pivot back to the instance
s.propB = 'PWNED'; // write; the class's own reads observe it

```

And the general one, which doesn't care about the class, the hierarchy, or `__protected` at all. `[_GET]` is a public method that iterates a `Set` with `for...of` — a dynamic `Symbol.iterator` lookup — so you just supply the iteration result:

```js
function steal(obj) {
	let state;
	const real = Set.prototype[Symbol.iterator];
	Set.prototype[Symbol.iterator] = function* () { yield (s) => { state = s; }; };
	try { obj[Symbol.for('jsProtectedGet')](); } finally { Set.prototype[Symbol.iterator] = real; }
	return state;
}

```

That returns the genuine `#_` — object identity, not a copy — for `C`, for `Sub`, and for a bare `new Base()`. It needs nothing but an already-constructed instance, invokes no real subscriber, restores the global it borrowed, and survives `Object.freeze(Base.__protected)`. `state.logState()` then _passes_ the pattern's own `if (_thys !== thys.#_) throw new Error('Unauthorized')` check — the implementation certifies the outsider's handle as authentic.

Also worth noting: `test/integration.test.js`, "protected state cannot be subverted after construction", calls `sub(new_)` and discards the return value. The subscriber is `(p) => this.#_ ||= p`, and `||=` short-circuits, so that discarded return value _is_ the protected state. The test executes the leak and concludes the opposite:

```js
const subs = new Set(); victim[Symbol.for('jsProtectedSub')](subs);
const state = [...subs].map((f) => f()).find(Boolean); // no argument, so nothing is overwritten

```

To save the patch cycle: these aren't bugs. I worked through the obvious hardening - freeze the statics, module-local `Symbol()` instead of `Symbol.for()`, `defineProperty` for ` __this`, drop `__ this`, `new.target.__protected`, block-body subscribers, freeze every prototype - and there is no fixed point. `Symbol()` buys nothing, because the key is recoverable via `getOwnPropertySymbols` off the public prototype chain. Dropping `__this` _adds_ a hole, since `__this` is the only authentication a protected method has. `catch (_) {}` turns out to be load-bearing: remove it and the pattern stops working, because every intermediate `[_GET]()` necessarily throws on the not-yet-installed derived `#_`. And the last one cannot be closed at all — the moment the shared object is _used_, it crosses a call boundary resolved at runtime (`console.log`, `Object.keys`, any callback), and a single global write catches it there.

Which is the actual point, and it isn't about this implementation.

JS's encapsulation primitive is **unreachability** , and reachability is a property of the object graph. `protected` is a property of _the code that is asking_. JS has no runtime notion of caller identity — nothing can ask "which class body is this call lexically inside?". `#x` works precisely because it never asks: it is resolved lexically, at parse time, in exactly one class body, and no channel to it exists. That is why private fields do satisfy "only".

Protected differs in kind, not degree. `#_` in `Base` and `#_` in `Sub` are different names, so the shared thing must be _distributed_ between them at runtime - and every runtime channel in JS is reachable by anyone who can run code in the realm. That's the whole trade: unreachable but unshared, or shared and therefore reachable. There is no third state to build on, which is why no amount of efficient simulation gets you there. The leak isn't in the mechanism; it's in the requirement.

Your insider/"friend" pattern is the interesting confirmation of this. That one does work - because it is capability-based: possession of a token, which is exactly reachability. It maps cleanly onto what the language actually has. The one that doesn't map onto reachability is the one that doesn't survive contact. And a module-scoped `WeakMap` closed over by the classes that should share the state gives you every guarantee `protected-js` reaches for with none of this surface - the honest description of which is "private state, shared lexically", not an access level.

So it isn't a category error because the ergonomics are bad, or because nobody has been clever enough yet. It's a category error because "only" is a claim about callers, and JS has no callers to interrogate.

---

<div class="post-metadata">

**Author:** ![bkatzung](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bkatzung/32/2721_2.png) [@bkatzung](https://es.discourse.group/u/bkatzung)\
**Post date:** [September 1, 2026, 3:34am UTC](https://es.discourse.group/t/protected-support-for-class/68/31 "2026-09-01T03:34:45Z")

</div>

Thank you for the very detailed analysis and feedback, Jordan. I'm incorporating your fixes.

I understand it has limitations (I think we can agree JS is not a security-minded language), but so does `protected` in TypeScript, and so do `WeakMap`s, and bound functions, etc. and people still use those. I believe some people will still find the option and trade-offs it provides useful at some level.

If it's only me, I can live with that.

---

<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:** [September 1, 2026, 4:43am UTC](https://es.discourse.group/t/protected-support-for-class/68/32 "2026-09-01T04:43:05Z")

</div>

It’s in face one of the most secureable languages on the planet :-) see the Hardened JS project (caja etc). It just can’t provide security by incompatible mental models, such as “access levels”

---

<div class="post-metadata">

**Author:** ![rdking](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rdking/32/54_2.png) [@rdking](https://es.discourse.group/u/rdking)\
**Post date:** [September 12, 2026, 2:42pm UTC](https://es.discourse.group/t/protected-support-for-class/68/33 "2026-09-12T14:42:18Z")

</div>

If anyone is still interested, I have my own thorough [implementation of protected](https://github.com/rdking/cfprotected) support that avoids issues Jordan brought up to me in previous years. To his comment about "access levels" not being supported, I can only say that the same thing was originally true of a lot of features that are now common place in JS, even if regrettably so. It's not a matter of whether or nor "protected", or "access levels", or whatever you want to call it is something JS has. It's a matter of whether or not it's a feature the users want that can be reasonably implemented.

Describing private fields as "closed over variables in a function body" does them a severe disservice. They are variables owned and scoped to a definition instead of an active object or closure. That's something JS did not have prior. Neither is the enforcement logic behind "use strict" or the internal tagging behind "class", etc... I could keep going.

What people want is simply the ability to share access to member functions with classes that extend it. That's not "access levels" in the way languages supporting "protected" present it. Instead, it's a 1-time passing of a reference object that can be kept in a slot just like private variables. However, unless something has changed over the past few years, Jordan is probably still willing to "die on a hill" before seeing anything like this implemented. What I cannot understand is why.

[Previous page](https://es.discourse.group/t/protected-support-for-class/68.md?page=1)
