Incidentally, we should also discard `this`, unless directly inside an instance method of a class.
Incidentally, we should also discard `this`, unless directly inside an instance method of a class.
If you really haven't found a use case for call/apply/bind, or are confused by `this`, it's more likely that your focus has been on thin-client user facing applications for most of your career. These language features are invaluable for tool building, and are foundational to most of the tools you probably leverage to make thin-client user facing apps
NOTE: most developers work in thin-client user facing applications, that's not a dig or anything, it's just where 99% of the work exists
I would still remove them in favor of arrow functions and spreads. Unless you can show me any code which is pivotal to writing some tool which absolutely can not be done using the alternatives.
proxies and wrappers are two areas where you need call because you need be able able to pass the correct "this" which arrow functions will fail to do
It’s not that I inherently think call/apply/bind are bad (JS programmers should understand them), it’s just that they are old JS-isms and arrow functions/map/forEach/etc. follow pretty consistent conventions across multiple programming languages (readability) and with JS compilers you mostly don’t have to explicitly write JS in the old way any more.
It’s kind of like using malloc/variants in C++, you should be able to understand it, but you mostly shouldn’t need it in new code.
This is still used a lot in recursive polymorphic code like virtual dom, AST traversal, deep cloning, etc.
EDIT: My example is bad, you could just `[...arguments]`
When's the last time you considered using a with(x){} block in JS? Would you still use var instead of let or const?
`call/apply/bind` are normal functions though, not some fancy syntax construct, and `this` is important if you want to play with prototypes/Object.create, which is what you want to do if you found class syntax sugar lacking in features/extensibility/overrideability.
Entire Javascript needs to be removed in favor of some other language. It's literally garbage. Callback hells, prototypal inheritance, constant new features ala async/await, Promises and a new framework every 2 years. It's not even a functional langauge just some features of functional language. Endless introduction of new syntactic sugar to cover up the old fuck-ups and weird behavior on every corner.
If you want to write future-proof software, you have to do it as simple as possible.
Then again, I think we're already well on that path with some languages' ecosystems... if you don't want your jobs replaced by idiots, maybe you should not write code for idiots.
How does it add confusion?
Bind, call and apply are fairly straightforward and not difficult concepts to grasp for any mid to advanced level JS dev and they're useful in many scenarios (although bind is less useful / less needed now that we have arrow functions, but call and apply are definitely not something to be "discarded").
So, do this instead:
Array.from($nodeList, myFn)
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...Also serves as a great example of how JS is standardizing useful patterns that were previously accomplished via bending the language a bit.
Personally I think Array.of($nodeList).map(…) should be guaranteed to allocate once and then there’d be no more discussion here.
https://262.ecma-international.org/7.0/#sec-array.prototype....
const mapNodeList = (...nodeList) => nodeList.map(myFn);
mapNodeList(...$nodeList) const mapNodeList = (...nodeList) => nodeList.map(myFn);
mapNodeList(...$nodeList)
Garbage collection really isn't the biggest of concerns here considering this code is susceptible to a stack overflow...Array.from(nodeList$).map(myFunc)
or if you want to spread
[...nodeList$].map(myFunc)
In this case, the code size is about the same if you run as-is (if you're doing DOM manipulation, performance is likely a real concern). I'd agree it would be premature optimization if we were talking about moving from a map to a full-blown loop or something, but it's hardly a huge change.
Another great example is chaining myarr.filter().filter().map().map() or some such that I sometimes see. Lodash shortcuts normal map with the assumption of arrays without holes and then uses their own iterators to avoid multiple copies. This results in a huge performance boost.
A final consideration here is transpiling. The above methods would actually increase the code size quite a bit trying to work around any edge cases and hit performance even more.
"Premature Optimization" is always a matter of opinion. Some people might consider implementing quicksort instead of bubble sort to be a premature optimization. Others might disagree with me and believe that you should always use loops instead of forEach or map. In any case, this is hardly the only case for using call/apply.
Unless your nodelist is insanely large, this is going to be a trivial GC to perform.
But the web just feels like it’s getting slower, grinding up CPU. I think in aggregate, this stuff does matter and it’s just wishful thinking to say it doesn’t.
It’s like at some point as a profession we took the mantra of “don’t prematurely optimize” as an excuse.
Honestly, js feels like a much better language today, especially with ts, and hopefully there are improvements that can be made with modern approaches at the JIT layer, but until then, I’d love it if we could just have a more performant web. It might not matter to you on your powerful dev machine, but it does to me now that I’ve given up the powerful dev machine.
I don't think the web is getting slower because we're using more map() instead of for...of loops. It's getting slower because what is being developed is getting more complex.
Feature bloat and excessive network load are the main reasons that the web feels slower imo.
I can tell you that when I navigate around the web doing nothing more than having some web apps open, the UI for the whole OS will start stuttering and the fans will kick into action. `top` says this is all chrome.
I buy that the problem is “feature bloat,” but I also think a great deal of these features are possible with a heck of a lot less code and a heck of a lot less pointless GC etc.
.filter().map() may not itself be alarming for a few hundred elements…but it is for a few hundred elements multiplied by a few hundred features. If you still want those few hundred features, maybe it would be nice if some thought had gone into making them individually perform better.
Tangent: I love the Unix philosophy! Do one thing well. Nothing more satisfying than being able to pipe inputs to outputs and put together little programs out of modular components. But when you want to build a program, you don’t do that, because you care about the “well” part of “do one thing well.”
Context does matter for a lot of this, and I think there’s probably a lot of grey area here. Doing something high level? Whatever, just get the job done, focus on readability, don’t worry about the weird performance edges of it. It’s just that those chunks have a tendency to become bigger chunks and get included where they were never intended, or where their underlying implementation is accidentally replicated multiple times for slightly different outputs (in the abstract, like doing .map().map().map()).
Anyway I can tell I’m losing the thread and ranting mindlessly now…
arrow binds a different "this" than call and apply. Call and apply get the this passed in at call time. Arrow gets the this at creation time.
Just use vanilla functions and objects, and when in need of something that looks like a class, reach for the revealing module pattern:
https://gist.github.com/zcaceres/bb0eec99c02dda6aac0e041d0d4...
It also makes specialization and calling super methods super verbose.
And you've ultimately not done anything other than reimplementing OOP, poorly.
OOP inheritance does not belong in this pattern, even if you could hamfist it in (which you've already observed as unwieldy).
Is there a better way?
But even conceptually there are problems with testing private methods. For example they likely expect to be called only while the class is in some specific state or in the middle of some atomic operation. Otherwise they could have just been made public.
Maybe I don't write very complicated material, but I can't think of a single time I've needed to use the more OOP features of JS because one of them was the best or only solution.
I’d say all of the articles on the internet are rehashing the spec in (at best) a dubious and ritualistic way, and have zero technical value if you ask me.
Do-overs not possible, by better new forms over time helped. Honest q: Do you actually use modern JS at all?
Thanks, but no thanks. I'll take clear, understandable code over clever footguns any day.
We should discard classes instead. People have trouble understanding `this` because they insist on thinking in terms of classes and methods which are second class concepts in Javascript. Once I understood objects and prototypes, `this` also became easy to understand.
Programming with Object.create produced the cleanest code I've ever seen. Nothing but functions, objects and prototypes. I simply don't understand why the new versions of Javascript insist on adding even more of this class stuff.
Edit: actually I did use .bind with React before arrow functions came
As of source code or p-code or by whatever structure {<code>} body is represented inside, it is always* static for all closures, so there is no overhead apart from a closure itself between:
function foo() {<code>} // func
for (;;) foo()
and let i = 0
for (;;) {
let j = 0
function foo() {i++; j++; <code>} // closure
foo()
}
Because {…<code>} is a singleton in both snippets.* of course jit may blur this distinction a lot, but that’s unrelated
const {filter} = []
class Foo {
less() {
return filter.call(this, sometest)
}
}
There is no way to closure it somehow without (temporarily) modifying an instance of Foo. (The next obvious question is “why” and the answer is “metaprogramming”.)When you say it has to be "directly," do you mean we should never use arrow functions (or use them only for looks)? Since the purpose of those is to add "this" support to inner functions?
This call helper is often used to avoid prototype pollution vulnerabilities when patching native prototypes.
I do a lot of work on SDKs which run in semi-hostile environments, so prototype pollution is something I'm frequently running up against.
Correct. I go into more detail about into our problem space and use cases here: https://transcend.io/blog/defeating-cookie-banners
Yep, and I think that determining `this` at the call-site is really awful, and has been the cause of many more bugs than is reasonable.
OOP you can do in JS with just `class` and `extends`, you no longer need to care too much about prototypes and how they work. Prototypes have become much more of an implementation detail in recent years.
That’s what almost all (more or less) dynamic languages do, except those that don’t have an arbitrary “object” struct. Out of few dynamic languages only python does what you need, at the cost of the enormous GC pressure. It’s okay for python because it was meant to be slow as hell by design.
And even then the solution would be more error-prone than the “issue” itself. Consider this code:
app.use(sass({
src: “.”,
log: console.log,
log2: function () {…},
}))
Should the anonymous object overtake console.log()’s “this”? And for log2()? And if you create an object and assign a bunch of functions to it in a literal? Later in the code?Your frustration is understandable, but I assure you that python’s “bound methods” are really awful to debug as well (been there done that), because the issue is not in functions taking “this” one way or another, but in a mismatch between developer’s expectations and reality.
I get around that in js mostly by avoiding classes and `this` entirely unless I'm sure I'm going to have a large number of instances of something. But there is the mental overhead of "Do I need to wrap this function in an arrow function before passing it around to avoid getting an unexpected `this`?"
So yeah, not saying I have a great solution -- just that none of the existing solutions really seem ideal.
First of all, it’s just a single-handed point of view. I’m using them from time to time, because js doesn’t end with reactjs hooks or frontend, and even frontend doesn’t end with just writing “frontend glueware”.
But even if it did, what exact mechanism do you propose to bind methods to instances? Automagic? It’s not just an “implementation detail”, it is a way of doing OOP in dynamic languages. Do you want all your objects to contain references to bound copies of all of the inherited methods? The way it is is for a reason, and the reason is, it doesn’t come for free.
There was once a time when such 'js ninja' skills were appropriate, but now they almost represent an anti-pattern in normal usage.
For some kind of framework, maybe.
But otherwise, they're just going to cause problems and they don't really provide something really valuable wherein another more basic use case wouldn't be better.
A pragmatic approach might be to 'stick to a solid version of Typescript' and even then be wary of the fancy stuff unless it's truly needed. (Say, you're building a lib or some core module).