HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
tc39
proposal-class-fields
HTML2017–2023
tc39 / proposal-class-fields · Archived

proposal-class-fields

Orthogonally-informed combination of public and private fields proposals

32Distinct people
39Mentions
1Explicit recommenders
1.7KGitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

Orthogonally-informed combination of public and private fields proposals

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

Watching how the language grows can be very interesting. I highly recommend taking a look at TC39 proposals and the process. For example, SmooshGate:…

otras used a recommendation phrase. One indexed comment matches. This is a phrase match, not a verdict.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

Mentioned in the same Hacker News threads. That is not proof they are substitutes.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 8:59:20 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year39 dated mentions

Observed mentions: 2017-08-03 17:19:41 UTC to 2023-05-30 07:30:05 UTC.

051015202017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 5 mentions; 1 submissions, 4 comments20172018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 20 mentions; 1 submissions, 19 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 10 mentions; 0 submissions, 10 comments2020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments20202021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 4 mentions; 2 submissions, 2 comments2023

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC514
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC20119
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC10010
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC000
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC000
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC000
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC422

From the project README

An excerpt from the repository, fetched Nov 13, 2026.

Read the project’s introduction
Class field declarations for JavaScript Daniel Ehrenberg, Jeff Morrison Stage 4 A guiding example: Custom elements with classes To define a counter widget which increments when clicked, you can define the following with ES2015: class Counter extends HTMLElement { clicked() { this.x++; window.requestAnimationFrame(this.render.bind(this)); } constructor() { super(); this.onclick = this.clicked.bind(this); this.x = 0; } connectedCallback() { this.render(); } render() { this.textContent = this.x.toString(); } } window.customElements.define('num-counter', Counter); Field declarations With the ESnext field declarations proposal, the above example can be written as class Counter extends HTMLElement { x = 0; clicked() { this.x++; window.requestAnimationFrame(this.render.bind(this)); } constructor() { super(); this.onclick = this.clicked.bind(this); } connectedCallback() { this.render(); } render() { this.textContent = this.x.toString(); } } window.customElements.define('num-counter', Counter); In the above example, you can see a field declared with the syntax x = 0. You can also declare a field without an initializer as x. By declaring fields up-front, class definitions become more self-documenting; instances go through fewer state transitions, as declared fields are always present. Private fields The above example has some implementation details exposed to the world that might be better kept internal. Using ESnext private fields and methods, the definition can be refined to: class Counter extends HTMLElement { #x = 0; clicked() { this.#x++; window.requestAnimationFrame(this.render.bind(this)); } constructor() { super(); this.onclick = this.clicked.bind(this); } connectedCallback() { this.render(); } render() { this.textContent = this.#x.toString(); } } window.customElements.define('num-counter', Counter); To make fields private, just give them a name starting with #. By defining things which are not visible outside of the class, ESnext provides stronger encapsulation, ensuring that your classes' users don't accidentally trip themselves up by depending on internals, which may change version to version. Note that ESnext provides private fields only as declared up-front in a field declaration; private fields cannot be created later, ad-hoc, through assigning to them, the way that normal properties can. Major design points Public fields created with Object.defineProperty A public field declarations define fields on instances with the internals of Object.defineProperty (which we refer to in TC39 jargon as [[Define]] semantics), rather than with this.field = value; (referred to as [[Set]] semantics). Here's an example of the impact: class A { set x(value) { console.log(value); } } class B extends A { x = 1; } With the adopted semantics, new B() will result in an object which has a property x with the value 1, and nothing will be written to the console. With the alternate [[Set]] semantics, 1 would be written to the console, and attempts to access the property would lead to a TypeError (because the getter is missing). The choice between [[Set]] and [[Define]] is a design decision contrasting different kinds of expectations of behavior: Expectations that the field will be created as a data property regardless of what the superclass contains, vs expectations that the setter would be called. Following a lengthy discussion, TC39 settled on [[Define]] semantics, finding that it's important to preserve the first expectation. The decision to base public field semantics on Object.defineProperty was based on extensive discussion within TC39 and consultation with the developer community. Unfortunately, the community was rather split, while TC39 came down rather strongly on the side of Object.defineProperty. As a mitigation, the decorators proposal provides the tools to write a decorator to make a public field declaration use [[Set]] semantics. Even if you disagree with the default, the other option is available. (This would be the case regardless of which default TC39 chose.) Public fields are shipping in Chrome 72 with [[Define]] semantics, and this decision on semantics is unlikely to be revisited. Fields without initializers are set to undefined Both public and private field declarations create a field in the instance, whether or not there's an initializer present. If there's no initializer, the field is set to undefined. This differs a bit from certain transpiler implementations, which would just entirely ignore a field declaration which has no initializer. For example, in the following example, new D would result in an object whose y property is undefined, not 1. class C { y = 1; } class D extends C { y; } The semantics of setting fields without initializers to undefined as opposed to erasing them is that field declarations give a reliable basis to ensure that properties are present on objects that are created. This helps programmers keep objects in the same general state, which can make it easy to reason about and, sometimes, more optimizable in implementations. Private syntax Private fields are based on syntax using a #, both when declaring a field and when accessing it. class X { #foo; method() { console.log(this.#foo) } } This syntax tries to be both terse and intuitive, although it's rather different from other programming languages. See the private syntax FAQ for discussion of alternatives considered and the constraints that led to this syntax. There are no private computed property names: #foo is a private identifier, and #[foo] is a syntax error. No backdoor to access private Private fields provide a strong encapsulation boundary: It's impossible to access the private field from outside of the class, unless there is some explicit code to expose it (for example, providing a getter). This differs from JavaScript properties, which support various kinds of reflectio
Read the full README on GitHub

What Hacker News says

20 sources
First, about accessing private fields on members of the same class: the spec will allow this via `other.#foo`. The reason I said that the difference between `object.foo` and `object.#foo` is not purely syntax is because they are two completely different actions. The first is a lookup in the object's property map/prototype chain. The second is a private field lookup. `object.foo`: 1. Check to see if there's a property named "foo" in `object`'s property map. 1a. If yes, return the value. 1b. If no, continue. 2. Does this object have a prototype? 2a. If no, return undefined. 2b. If yes, repeat these steps starting from 1 using this object's prototype. `object.#foo`: 1. Test to see if you're in a class. 1a. If no, throw a Syntax Error. 1b. If yes, continue. 2. Test to see if `object` is an instance of this cl
Read the rest
ass. 2a. If no, throw a Syntax Error. 2b. If yes, continue. 3. Test to see if the class defines a private field *#foo*. 3a. If no, throw a Syntax Error ? (Not positive on this one, it might just return undefined). 3b. If yes, continue. 4. Look up this class' private fields for `object`. 5. Get the value for *#foo*. In case it wasn't clear: the private field `object.#foo` is NOT a property on `object` named `foo` and marked private. It is a field which exists in a namespace only accessible to the class definition. Without the # sigil, there'd be two issues. See [0] for a pseudo example of how private fields really behave. The first issue comes from a decision made in the spec by the committee: private fields must not interfere with object properties. This essentially means that an object can have the private field #foo AND the object property "foo" (as I described here [1]). Without the #, the fields would have to exist in the same namespace as the object's properties. If you error out when `object.foo` is accessed outside of the class definition, it would not fit the spec's definition of "private". [2] [3] The second issue is that without the #, the compiler would need to determine at the call site which lookup method to use, and they decided this would incur too much overhead on every single lookup in the already complicated property lookup logic. 0: https://news.ycombinator.com/item?id=18707421 1: https://news.ycombinator.com/item?id=18706363 2: https://github.com/tc39/proposal-class-fields/blob/master/PR... 3: https://github.com/tc39/proposal-class-fields/blob/master/PR...
In V8 release v7.2
The only one that has major implementation changes is the Public Class Fields proposal, which has positive public signals from other browsers: Proposal - https://github.com/tc39/proposal-class-fields Firefox Implementation Tracker - https://bugzilla.mozilla.org/show_bug.cgi?id=1499448 Safari Implementation Tracker - https://bugs.webkit.org/show_bug.cgi?id=174212 The others are largely security updates which honestly I'm fine with browsers pushing ahead with. The User activation query API stuff is great and is gonna solve a lot of headaches around misbehaving sites. Safari already has a rudimentary implementation around this with autoplay as well, so this is an effort to standardise some existing "monoculture bullshit". As far as depreciating TLS 1 and 1.1, I don't think there's anyone out there arguing that that isn't a good idea.
In Chrome 72 Beta: Public class fields, user activation and more
There is a lengthy debate surrounding private field syntax [1][2]. As a TypeScript user, I prefer regular-JS drop this proposal altogether, rather than use the current syntax. Refactoring and index-access notation become awkward with it. Anecdotally, the vast majority of serious developers I know transpile to JS. [1]: https://github.com/tc39/proposal-class-fields/issues/177 [2]: https://github.com/tc39/proposal-class-fields/blob/master/PRIVATE_SYNTAX_FAQ.md
In Chrome 72 Beta: Public class fields, user activation and more
The solution that TC39 voted is based on requirements that can only be satisfied by having the ugly `#`. Namely they voted for encapsulation / hard-private [#33] as well as comparing private properties of two classes [2], which as a result makes the only possible syntax the one with the `#`. Unfortunately this comes at the price that once browsers implement this it will become history ( same as the `with` statements [3] and `labels` with `continue` [4] ). Because of backwards compatibility these will never be removed. Maybe in 2022 TC39 will decide that "use static-type;" will be a mode, such as strict mode, where you can write statically typed JS ( great idea, btw ), where this proposal will make no sense. Another "use class-inheritance;" mode would also finally end the abuse of prototype inheritance with syntax sugar. In this future the
Read the rest
In V8 release v7.2
I'm honestly not sure, but even taken in isolation it's a convincing argument to me. See [0] for some info straight from the proposal, but the gist is doing it this way makes it so that you never have to worry about name collisions for private fields. Subclassing, superclassing, and object "monkey patching" all can continue to work as-is with no changes, and none of them have to worry about the existence of private fields complicating implementations, and the classes with private fields can feel free to change and rename them as much as possible, even within patch versions of libraries, since they cannot impact any code outside of the class under any circumstances. That's a pretty powerful guarantee to give which GREATLY simplifies working with them. If I want to extend the react component class, I don't need to worry that my `#component`
Read the rest
private field is going to break if an update to the react component class adds their own `#component` field, or if a user of my library tries to do something like this: const fancyObj = new FancyObj() fancyObj.tag = Symbol('tag') Which I'll admit to having used in some cases where I want to tag a bunch of otherwise identical objects which will be thrown into an array and then be able to easily pluck out the ones I tagged later. Without true encapsulation, that code above would break if they changed the private interface to use the `.tag` field internally, and I would have no idea because it is consitered a private interface and wouldn't need to be documented or maintained across versions. [0] https://github.com/tc39/proposal-class-fields/blob/master/PR...
In V8 release v7.2
I really recommend you read over the FAQ that is in the proposal at [0], it answers most of your questions. The problem is that it seems a "private by convention" system isn't private enough for many library authors, who are finding their private internal methods being used regardless, and then after some time becoming ossified leaving the maintainers in a tough situation. Support the "private" interface even though they shouldn't have to to avoid breaking a significant number of users, or break the "private" interface and leave many developers with code churn. Regardless of where fault lies (often times it's one library using the private interface of another library, and the developer gets caught in the middle), this is a bad thing, so people began workarounds like complicated closure systems or weird-looking interfaces which emulated tr
Read the rest
ue private fields without having support for them. This started becoming popular enough that it was decided that adding them to the official syntax would be better, and unlike in PHP or other languages, there is no way around it here. Serializing or casting the object doesn't expose private fields, and there is no reflection methods or anything that would allow it. They are truly private. I personally share your feelings that this isn't an issue that needs solving (i'm of the opinion that the lack of easy truly private properties had a non-zero impact on the rise of javascript, and the ability to tweak private interfaces in your code is a net benefit, even if it gives developers rope to hang themselves with), but the pain felt from it is real for many, and the workarounds they were using were not only slow and difficult to use and maintain, but also very hard to understand, and this alternative is a much better solution in my opinion. So while I most likely won't be using them, I reluctantly support them being added to the language. [0] https://github.com/tc39/proposal-class-fields/blob/master/PR...
In V8 release v7.2
If you think that the # syntax for private fields needs to be improved, then go to JS Committee's GitHub Issues and give feedback about it: https://github.com/tc39/proposal-class-fields/issues There's at least three specifically about the #: https://github.com/tc39/proposal-class-fields/issues/177 https://github.com/tc39/proposal-class-fields/issues/149 https://github.com/tc39/proposal-class-fields/issues/142 Remember to be constructive or just give a thumbs up to comments you like.
In V8 release v7.2
Not as far as the spec is concerned. They decided that private variables should be completely scoped to the class. "Having a private field named x must not prevent there from being a public field named x, so accessing a private field can't just be a normal lookup. This is only an issue in JavaScript because of its lack of static types. Statically typed languages use type declarations to distinguish the external-public/internal-private cases without the need of a sigil. But a dynamically typed language doesn't have enough static information to differentiate those cases." [0] There are also performance concerns to changing the lookup logic without the sigil. More info in the link. 0: https://github.com/tc39/proposal-class-fields/blob/master/PR...
In V8 release v7.2

Showing the 15 most recent of 20 sources. Enable JavaScript to search and browse every indexed mention.

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.