10 ms·
JavaScript private class fields considered harmful
- auggierose 4y agoInteresting read. I am drawing the opposite conclusion: I should avoid both Proxy and Vue.js!
- hajile 4y agoDon’t forget to write off reactive libraries and a bunch of other stuff too. The proxy problem is bigger than the “people don’t respect my authority” problem that led to private fields in the first place.
- auggierose 4y agoI've read the documentation about Proxy, and I am still not sure what it does, and why the hell I would ever want to use it. Libraries relying on such a feature are best avoided, in my opinion. Actually, most JavaScript libraries are best avoided :-)
- migf 4y agoYyyyyeah, classes in js are pretty bad full stop. For instance, passing a method as an argument will do awful things unless you bind it to a this first. Gross. Still, it's interesting to me that Proxy is more valuable to author than private member variables. Curious about some common use cases.
- eyelidlessness 4y agoProxy is commonly used to wrap reactive object values, which is the use case the author identified as an issue.
- graypegg 4y agoTotally agree. This is very much a symptom of JS’s class-ish syntactic sugar. Access modifiers have to be a runtime check, and be bolted onto an environment that was built around function scope being the only way control access. Using typescript to handle things like access modifiers at compile time makes everything much more predictable.
- dexwiz 4y agoDoesn’t typescript just transpile to closure based access control?
- afiori 4y agoClosures are a much better "class" primitive than JS classes
- nosianu 4y agoTypeScript's "private" is only for type checks. It is simply removed. You may also want to read what I wrote a few days ago already: https://news.ycombinator.com/item?id=35653653 https://news.ycombinator.com/item?id=35653653 TypeScript now supports both the ECMAScript #private - it has no choice in the matter, since it's in the language now - and their own development-time "private". It sounds messy but that's the fault of those who wanted it in the language, as runtime checks. I agree with the other comment, closures are far more powerful and flexible. I understand that this OOP stuff in JS was probably a nod to the many programmers in many companies already used to it, to make JS more palatable for them. It's easy to demand millions of people worldwide to adapt, but in business reality it does not work that way. So I grudgingly accept those new(er) things in JS as a business decision and necessity, for the huge crowd of people doing programming mostly just for the money, who had to write more and more code for the web that used to be run as installable software on Windows.
- dexwiz 4y agoEven without classes there are a ton of footguns involving ‘this’ and method scope. Fat arrow notation papered over a bunch of issues. But I have lost more than a few hours confused as to why changing between map and a loop completely broke/fix behavior. Or seen codebases absolutely peppered with unneeded ‘bind(this)’ like it would ward off evil. I had to spend some time studying before I really grokked it.
- pwdisswordfishc 4y ago> For instance, passing a method as an argument will do awful things unless you bind it to a this first. That was true long before classes arrived.
- mst 4y agoIt continues to sadden me that the implicit this injection happens only on direct invocation - let res = obj.method(arg); and let m = obj.method; let res = obj.method(arg); being different things seems like an unfortunate trade-off (though not knowing the full reasons it was chosen to be that way makes me wary of assuming it was the wrong decision overall). I've been known, for things where the object is e.g. a bag of components, to loop at construction time and replace all of my methods with pre-bound copies of themselves, but having to make a conscious decision as to whether I need to introduce that extra code (and work for the runtime per-instantiation) still makes me grumble to myself every time.
- llamaLord 4y agoCan you explain wrist you mean by those two examples being different things? Do you mean that 'res' evaluates to a different result in each? Or that when you call 'object.method()' vs calling 'm()' you would get a different 'this' context?
- mst 4y agoI completely messed up one of the examples, sorry. I meant the 'this' thing - a sibling comment to yours contains the correct code.
- majou 4y agolet m = obj.method let res = m(arg)
- mst 4y ago-facepalm- Thank you.
- imbnwa 4y ago>Yyyyyeah, classes in js are pretty bad full stop. For instance, passing a method as an argument will do awful things unless you bind it to a this first. Gross. One of the things that heavy use of JS frameworks will do is make you forget you can pass an object interface to represent continuations. The DOM spec defines the EventListener type, in TypeScript notation, as: `(event: Event) => void | { handleEvent(event: Event): void }` What does this type signature instruct us to think? That a function closure should reference objects in its scope, no need for `this` binding, or an object interface, appropriate to the domain of the deferred caller should be referred to and called (e.g. `handleEvent` in the case of DOM's EventTarget interface, but one could also imagine `{ onTimeout() : void }` if the spec authors were consistent, perhaps accepting a key to differentiate calls in the same vein as event types), also eliminating the need to bind `this`. The addition of `WeakMap`\`WeakSet` to the runtime eliminates the need for manual resource management. But React, the most popular JavaScript library (and its not alone in this), makes this pattern impossible: you can only pass functions to event listeners. So everyone then growls about `this` binding.
- eyelidlessness 4y agoThe article (and author’s referenced tweets) center on private field access failures with Proxy, but (and) the same problems arise if you override subclass members which defer to their super for private access. This is something that works perfectly fine with the `private` keyword semantics in TypeScript which predate runtime private class members. I agree that the actual runtime behavior is harmful, and I will only use it very carefully with very skeptical consideration of whether there’s any value in using it. Fortunately symbols provide exactly the same runtime encapsulation semantics with none of the footguns. It’s a little bit more ceremony, but you can achieve everything a private class field would accomplish by using singleton symbols as long as you don’t export them or otherwise expose them to a consuming scope.
- i386 4y agoIsn’t the point that private fields remain private implementation details of the class in just about every language that has them? As a library author, if I declare something as private, it means you the consumer, may damage internal state in some way by directly mutating those fields. If they do need to be read or mutated, that’s a good case for adding to the public API, not complaining that reflection tricks can’t get at something you shouldn’t have access to. Not to mention how incredibly fragile your consumer is going to be when upgrading… I feel like the JS folks are learning the same lessons the hard way that the Java folks did 25 years ago.
- jmathai 4y agoIsn’t most Javascript rendered useless by the time it is ready to be upgraded because all of its dependencies have moved on to not so greener pastures? On a serious note. Keeping up with changes in the Javascript community seems like a full time job. I don’t know how they get any work done.
- zdragnar 4y agoThis joke was already getting old 6 years ago. The major frameworks of today were already well established then, and the last really big update to the language in 2015 had already been in fairly wide spread use via transpilers (traceur and later 6to5, which became babel). Honestly, in the last 13 years, many mainstream languages have gone through similar churn. JavaScript has gone through more than most, to be sure, but nobody complains about Rust or C# nearly so much.
- beezlewax 4y agoJS is dependencies all the way down. Upgrading things can be a nightmare
- bilalq 4y agoAnd yet, I find myself struggling way more with Objective-C/Swift and Java/Kotlin deps than I do with JS ones. Dependency upgrades can be problematic, sure, but JS is no worse than any other language. I never had to change compiler flags or mutate internals of a library in npm the way I need to do with cocoapods.
- SSchick 4y agoPersonally I feel like private JS class fields are against the spirit of a scripting language and are harmful to debugging and inspectability (in the way they are implemented). I've stuck with TypeScript private fields which does exactly what I need.
- transfire 4y agoHad similar problems with Java developing for Android. Nothing like having to reimplement dozens upon dozens of methods all because you need one little change in one untouchable private method.
- sbdkrend 4y ago> you need one little change in one untouchable private method Tell me you don’t know how anything works while not saying you don’t know how anything works.
- rektide 4y agoIn general, the people writing language features out of fear cause the bulk of the problems. Js had coped quite well without having private fields. We had conventions of using this._field that served fine, that cautioned users sometbing was internal-ish without binding them, without preventing experimentation. The private function implementation is more bad language design seeming on an inevitable course to make the language worse & less capable. It's more fear, more adding nothing, more creating friction, like private fields. That all said, it doesn't seem like proxies ought be broken by private class fields. That's another heap of scorn placed upon this already tragic & harmful misfeature.
- claytongulick 4y agoI was amongst many who argued vocally against this proposal back when it was in stage 1. There was a huge community backlash against it, which tc39 blithely ignored. It made me lose confidence in tc39 completely. Whatever their motivations are, they are clearly not influenced by community feedback, at least not for this feature.
- ricardobeat 4y agoSame. At this point I've just given up hope on JS remaining a simple language, it’s already beyond repair.
- jwalton 4y agoI also argued strongly against this proposal. To me the biggest problem with private members, as it was implemented, is that is takes classes from “syntactic sugar on top of prototype inheritance” to something very different than that. It makes the mental mode of the language a lot more complicated, and without a lot of benefit. I never understood why TC39 was so intent on this “hard privacy” stance they took, as without it there were much better and more sensible compromises that could have been made. (Especially since JS is an interpreted language - if someone wants to work out what private variables are in your code, they can always just inspect the source.)
- 4y ago
- peter-m80 4y agoI just avoid classes in js
- nailer 4y agoIndeed. They’re very much implemented for people that wanted nineties style OO and who don’t get scopes/closures.
- pjmlp 4y ago> Self is an object-oriented programming language based on the concept of prototypes. -- https://en.wikipedia.org/wiki/Self_(programming_language) https://en.wikipedia.org/wiki/Self_(programming_language) > JavaScript is a high-level, often just-in-time compiled language that conforms to the ECMAScript standard. It has dynamic typing, prototype-based object-orientation... -- https://en.wikipedia.org/wiki/JavaScript https://en.wikipedia.org/wiki/JavaScript
- nailer 4y agoYes.
- 8n4vidtmkvmk 4y agowhahaha. i took the exact opposite approach and swore off proxies years ago for exactly this kind of edge case. there's scenarios where they just don't work and they'll bite you and i don't want to invite more gotchas into my code.
- afiori 4y agoThe author is not interested in writing proxy code, their interest is to allow other to use their classes together with libraries that happen to use proxies.
- 8n4vidtmkvmk 4y agoI guess that's noble to try and make your library compatible. I don't think they're giving up much by dropping native private vars. I'm in the camp of "if this var is documented as private/do not use and you use it anyway and then I change it and your code breaks, then that's on you". i.e. just prefix vars you don't want people to use with `_` or something.
- jschrf 4y agoI can't recall a single time in JS/TS, C# or even Java where I went "phew, glad i made that private!". In TS, i default to protected for internal implementation details. Why would one hinder future extenders by default?
- thfuran 4y agoUnless you're going 100% immutable everything (which the language isn't great for), advocating for making all members public in java seems roughly as sane as advocating for declaring all variables and returns as Object.
- oweiler 4y agoBecause extensibility should be a design decision.
- chii 4y agoextensibility not thought of during design is a flaw. which can be patched over if the actual internals are reachable.
- everforward 4y agoSure, but extensibility often comes with complexity. If I'm making something non-extensible it's to keep things simple or to prevent a bunch of bugs around people trying to extend something with complicated state. They always have the nuclear option of forking and making those fields no longer internal. They'll have to take on the maintenance overhead that I shirked, but that should be fine if they need it that badly.
- kriz9 4y agoI agree. Most of my frustration comes from unescessary abstractions and not code that I can't extend.
- chii 4y ago> They always have the nuclear option of forking and making those fields no longer internal. while that is always possible, it is not always reasonable. If you take firefox as the example, the removal of the XUL extension api is one such. And while there's many a forks to keep the old XUL api, none of them really caught on, and so support for them such as maintaining security patches etc fails.
- pwdisswordfishc 4y agoProxies exist pretty much for one purpose and one purpose only: to implement membranes, like described in <https://github.com/ajvincent/es-membrane/ https://github.com/ajvincent/es-membrane/>, where you build on demand a whole parallel universe of proxies that stand in for objects existing somewhere else, and have wrapper functions convert between those proxies and real objects. Any other use is incidental and unsupported. Admittedly, not many people know this, so those who are not deeply familiar with TC39 proceedings commonly think of proxies as just a way to hook into arbitrary behaviours of a single object, like property accesses, then are treated to a bitter surprise when proxies don’t work like what they thought or wanted. Still, the opening example is simply a misuse of Proxy, so it’s rather unconvincing. That said, with something like <https://github.com/littledan/proposal-new-initialize#another-use-case-simple-observed-classes-with-private https://github.com/littledan/proposal-new-initialize#another...>, something resembling the popular conception of Proxy might actually be made to work…
- NoahKAndrews 4y agoCan you elaborate on what's wrong with using then for other purposes?
- hajile 4y agoIf they didn’t want other use cases, they should have rethought the feature. The reality is that proxies came first. They see a LOT of use in reactive libraries making it very likely that your classes get passed into one these proxies (and breaking if private fields are used). They knew all this when ramrodding it through over the many protestors warning about this and other things. The fault is 100% on the hubris of the spec committee.
- mst 4y agoDatabase appears to have gone poof but somebody archived it beforehand: https://archive.ph/ffvTF https://archive.ph/ffvTF
- zeroCalories 4y agoIt seems to me that private fields are a feature best handled by a compiler. Such a runtime check will only be useful in very limited situations.
- andrewstuart 4y agoPrivate is a terrible concept. Only two days ago I chose to rewrite a chunk of code simply because I couldn’t call a private function that did precisely what I needed. Also “considered harmful” is an overused cliche I wish people would stop saying it.
- n2d4 4y ago> Only two days ago I chose to rewrite a chunk of code simply because I couldn’t call a private function that did precisely what I needed. It may have done what you needed in the current version of your library, but just look at it from the perspective of the library author. If they ever want to refactor, or maybe remove, that private function, it would break a lot of people's code. And even if it were marked private or deprecated, it would still break a lot of people's code. You could say "but if your code gets broken it's your own fault" and yes you're right, but it wouldn't stop those people from loudly complaining to the author.
- realusername 4y agoWhat's the alternative then? Maintaining a fork? It's the same update problems just with more hassle.
- Nullabillity 4y agoYes, maintaining a fork means that you're explicitly taking on the maintenance burden from that point on, and are on your own in terms of updates. That's a reasonable commitment to ask for if you're poking around in implementation details.
- ricardobeat 4y ago> you're explicitly taking on the maintenance burden How are you not taking the burden of maintenance when writing code that uses private-by-convention properties? The difference seems pretty abstract, a fork will require the same rework when internal properties change.
- afiori 4y agoPersonally this only reinforces how JS should have never gotten classes.
- pwdisswordfishc 4y agoPersonally this only reinforces how JS should have never gotten proxies.
- hajile 4y agoProxies added new features that couldn’t be emulated. Class syntax added nothing of intrinsic value, but also succeeded in creating an entire generation of “Senior” JS developers who don’t understand prototypal inheritance in general and the weird JS implementation in particular.
- dimal 4y agoThis just reinforces my bias that OOP is what is really harmful. Really, what good reason is there to have data and behavior bound to the same object? I’ve never seen a good argument for that. It’s just taken as a given. It seems nice and convenient, but that convenience seems to always lead to unexpected complexity like this. A while ago I was looking for a color manipulation library, and I saw her Color.js, and loved all the features. Then I saw the bullet point, “Readable, object-oriented API”. Nope. I’m not touching that. I chose culori instead. It’s just a collection of functions that transform standardized color data objects. It’s like lodash for colors. It’s been great to work with.
- lioeters 4y agoI agree. Private class fields seem totally unnecessary when you can simply "hide" functions and variables in a local scope and not export/expose it. (Or use symbols as another comment suggested.) I also don't want a library for color manipulation to be using Proxy and other magic tricks, including "this". I prefer plain functions that take any values they need as explicit arguments, and simply return results - without maintaining state, having side effects, pretending to be a plain object, etc.
- simplotek 4y ago> I agree. Private class fields seem totally unnecessary when you can simply "hide" functions and variables in a local scope and not export/expose it. This is not true. Private members are a part of a class interface, while local/internal function/variables are not. They both serve totally different purposes. If you're using private members as a substitution to local/internal members, you're doing it in ways that are fundamentally wrong.
- dimal 4y agoI’m confused by this comment. How are private members part of the class interface? They are internal, not exposed to the outside, so how can they be part of the interface, which by definition is what the class exposes to the outside?
- 8bitsrule 4y agoGreat thing about JS is you never need to use any OOP.
- pjmlp 4y ago> JavaScript is a high-level, often just-in-time compiled language that conforms to the ECMAScript standard. It has dynamic typing, prototype-based object-orientation... -- https://en.wikipedia.org/wiki/JavaScript https://en.wikipedia.org/wiki/JavaScript
- hajile 4y agoJS allows `”abc” > 124` and `with` but that doesn’t mean you’re forced to use them.
- pjmlp 4y agoIndeed, better not use strings and their methods.
- hajile 4y agoThat’s an absurd reductionist argument. I could argue that the actual implementation of them is subroutines, so it’s actually imperative programming, but that would also be absurd. There’s also a case that using those methods is indistinguishable from something like typeclasses which don’t involve classes at all. If we want to be even more pedantic, JS is prototypal and fundamentally lacks classes with objects simply having an optional pointer to B other objects, so the implementation of these methods doesn’t necessarily imply classes. You also have an argument that proves too much. If this argument is true, then your previous multiparadigm claim is proven false. In any case, it doesn’t seem like a good faith argument. There’s no reason I ever have to make my own classes in order to make projects in JavaScript. This is all a typical programmer is going to care about and is the spirit of my statement.
- pjmlp 4y ago
- ttfkam 4y agoOr use something like Svelte, which doesn't rely on Proxy objects for reactivity. Compilers > VDOMs + runtimes
- chclt 4y agoI still don't get what is wrong with the _ convention. Also please stop complicating the mental model of languages for no good reason (also looking at you C++).
- hajile 4y agoPrivate fields received more community pushback than any other JS feature by a huge margin (they even got more pushback than type coercion did). The spec committee gave the community the middle finger and did it anyway. Now we’re stuck with yet another giant footgun from their hubris. Because it’s so new, maybe they should just consider removing it from the spec.
- Emanation 4y agoIdk how you can conclude it's a problem with the concept of private fields when the giant shiner is Proxy not reflecting on them like you'd expect. It's borederline clickbait, or selfbait, as the author believes they've contributed to the ever shuffling stinky pile of hot takes that exist in the web world. It's really not as exciting to say Proxy, a niche class, than saying there's a problem with a concept of something every day, like private fields. I doubt the vast majority of you have any experiene with Proxy, but OOP? Im sure you've brooded on a few hottakes about that in your careers.
- hajile 4y agoYou care very much the second your reactive library mysteriously breaks. JS has modules that offer variable privacy that doesn’t cause all the issues of private fields.
- michael1999 4y ago+1 for pragmatism, and supporting users over abstract goals.
- flqn 4y agoIs this not more an issue with the proxy implementation? If it deferred to the proxied object's methods, would there be no issue with the proxy attempting to access its private fields? Apologies in advance if I'm misunderstanding the purpose of proxies.