24 ms·
How JavaScript Works: deep dive into call, apply, and bind
- bluepnume 5y agoCall, bind and apply should be discarded and forgotten about, they add too much confusion, there are better ways, and they end up being used as terrible interview questions. Incidentally, we should also discard `this`, unless directly inside an instance method of a class.
- kecupochren 5y agoI've literally never used them during my 8 years of working as a JS dev. Maybe I'm doing it wrong. Edit: actually I did use .bind with React before arrow functions came
- hajile 5y agoUntil for..of came around, `Array.prototype.map.call($nodeList, myFn)` was pretty much the way to iterate over things that were "array-like" but didn't actually inherit from Array, so didn't have such methods.
- nicoburns 5y agoThey were pretty widely used (especially in library code) before arrow functions were added to the language.
- jenseng 5y agoYou get "clean and DRY" at the expense of "not beginner-friendly" and "you need be to very careful". Thanks, but no thanks. I'll take clear, understandable code over clever footguns any day.
- user3939382 5y agoI figure there’s 3 possibilities: I’m just dumb, I don’t use it enough, or JavaScript is poorly designed. For some reason, even after 20 years of using JavaScript on and off and reading a dozen books about it, I can’t articulate exactly how the language works, how scope is evaluated, the composition of objects and prototypes which has some kind of turtles-all-the-way-down thing going on with it. When I read explanations in books it’s like hmm ok.. I guess that makes sense. Then a week later I don’t remember. The languages I’ve spent the most time with are SQL, PHP, Bash, and Clojure, and I never felt that way about them. Maybe it’s just me.
- nicoburns 5y agoPrototypes are quite simple. An object may have a prototype. If it does then that prototype is just another object, which is considered the first objects parent (which works with method calls like class based inheritance). The chain ends because it's also possible for an object not to have a prototype.
- wruza 5y agoI also had this trouble, because for all other languages you can just tead the spec once and that’s it. But ES specs are too technical to be used as such. After few years I just read non-boring parts of ES6 and it cleared all dark corners. 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.
- bluepnume 5y agoMy advice is to start of with javascript by forgetting 100% about classes, inheritance, prototypes, and so on. 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/bb0eec99c02dda6aac0e041d0d4d7bf2 https://gist.github.com/zcaceres/bb0eec99c02dda6aac0e041d0d4...
- rustyminnow 5y agoI've used this pattern before, but run into issues when you want to write tests for "private" functions (like loadSong() in this example). What I've done is name them like _loadSong and expose them anyways, but that doesn't feel great. Is there a better way?
- moron4hire 5y agoYou don't write tests for private methods.
- exdsq 5y ago
- normac2 5y ago> Incidentally, we should also discard `this`, unless directly inside a class method. 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?
- bluepnume 5y agoSorry, yeah, I did allow for closures inside class methods with that statement. That's fine by me.
- cj 5y ago> should be discarded and forgotten about, they add too much confusion 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").
- Arnavion 5y ago`call` and `apply` can also be replaced with arrow functions and splats.
- hajile 5y agoHow would you replace the classic `Array.prototype.map.call($nodeList, myFn)` with arrows and splat?
- nicoburns 5y agoYou could do it like this: const mapNodeList = (...nodeList) => nodeList.map(myFn); mapNodeList(...$nodeList)
- hajile 5y agoThat creates an entire extra list of garbage.
- chrisseaton 5y agoDoesn’t the compiler optimise it away?
- hajile 5y agoThose aren't the same types. One is a DOM array and the other is a JS array. They don't share the same constructor or the same prototype chain.
- ubaltaci 5y agothat list should also include anything that touches prototypes
- bluepnume 5y agoYou have my full agreement. You can write great javascript without ever touching prototypes, and probably even avoiding inheritance altogether unless you have a really succinct use-case.
- hajile 5y agoThere is literally NO substitute for either call or apply as they are low-level JS primitives. Arrow functions can go a long way toward handling .bind() scenarios, but I believe there are situations where you must still use .bind() to prevent constantly creating new functions (and the related garbage).
- bluepnume 5y agoShow me some code you can only write using either call, apply, or bind.
- wruza 5y agoAnything that substitutes ‘this’ but doesn’t want to put a function into that object. 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”.)
- bluepnume 5y agoI would rather re-implement `filter` in a way that is specific to `Foo`, than to attempt to use an array method on my class.
- wruza 5y ago.bind() does create a new function, though it has far less “slots” than a regular one. It doesn’t modify the original function. The spec doesn’t specify anything beyond that, afair, and if binds aren’t cached in some way (easy to check with === or roll up your own cachedBind()), GC barely can make any benefit out of collecting slightly smaller objects (but the interpreted part of a runtime may still process bound functions a little faster than closures doing the same thing). 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
- jollybean 5y agoI came here to write exactly that. 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).
- wruza 5y agoSadly (or not, really) there are no class/instance methods in javascript, cause its key lookup model is prototype-based and ‘method’ functions just get ‘this’ lexically (in x.y.f(), f() operates on x.y). The combination of these two facts builds up an entire OOP in js. If you really want to free your code of these ideas, it’s much easier to use some js-transpilable language out there.
- bluepnume 5y ago> in x.y.f(), f() operates on x.y 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.
- wruza 5y agoI think that determining `this` at the call-site is really awful, and has been the cause of many more bugs than is reasonable 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.
- bluepnume 5y agoI agree with you, it's definitely a trade-off between developer ease and perf. 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.
- JoeCortopassi 5y agoEvery language has parts that are harder to understand because they unlock the deep magic. 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
- bluepnume 5y agoI've worked full-stack in javascript for many years, and I've written plenty of libraries and tooling. I've used call/apply/bind/this extensively and I understand them to a fault. 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.
- JoeCortopassi 5y ago`Array.prototype.slice.call(arguments)` is a good example. In fact, I'm struggling to think why you would replace anything other than bind with an arrow function EDIT: My example is bad, you could just `[...arguments]`
- bluepnume 5y ago`Array.prototype.slice.call(arguments)` can be replaced with `Array.from(arguments)` (or as you mentioned `[ ...arguments ]`)
- asiachick 5y agoarrow functions and spread do not cover all use cases of call/apply/bind 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
- 5y ago
- Sephr 5y agoI've had to cache a secure reference to call() in a JS library intended to exist in potentially hostile environments. This call helper is often used to avoid prototype pollution vulnerabilities when patching native prototypes.
- bluepnume 5y agoVery interested to learn more about this use case. This is to protect against hostile environments blocking function calls by polluting 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.
- Sephr 5y ago> This is to protect against hostile environments blocking function calls by polluting prototypes? Correct. I go into more detail about into our problem space and use cases here: https://transcend.io/blog/defeating-cookie-banners https://transcend.io/blog/defeating-cookie-banners
- arcosdev 5y agoWe shouldn't use 'class' either
- Tade0 5y agoHow do you want to do partial application if not through bind? All the other methods are much more verbose.
- matheusmoreira 5y ago> Incidentally, we should also discard `this`, unless directly inside an instance method of a class. 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.
- monus21 5y agoMy one annoyance with these is that they aren't supported in Typescript i.e no return types.
- ash_gti 5y agoIf you enable strict (https://www.typescriptlang.org/tsconfig#strict https://www.typescriptlang.org/tsconfig#strict) or use the `strictBindCallApply` flag (https://www.typescriptlang.org/tsconfig#strictBindCallApply https://www.typescriptlang.org/tsconfig#strictBindCallApply) it should check the types on call, bind and apply.
- monus21 5y agoCool, I never knew that
- yawaworht1978 5y agoHow much call, apply and bind is abstracted away in reactjs? I also agree that the assignment of "this" is pretty difficult to grasp, especially in large codebases with callbacks, fn as arguments, and the issue still remains, despite fat arrow functions, async/await and promises. Someone mentioned prototypes, that's a double edged sword, it can help to have own prototypes but it can cause so much confusion.
- Jcampuzano2 5y agoapply/bind/call aren't really abstracted away at all in react. In older versions they did do the binding magically for you, but when they made the switch to es6 classes they stopped doing that in favor of writing JS more like vanilla JS with less magic, outside of JSX of course. Now with hooks/function components being the most common way of writing apps, there's almost no need for binding or dealing with "this" at all.
- peanut_worm 5y agoI have probably used these functions only a handful of times since ES6 came out
- eh9 5y agoI usually ace front-end interviews right until I feel insecure because I’m seriously asked about concepts I haven’t used in production after working in this field for 10 years.
- IggleSniggle 5y agoYup! I know how these work, but it’s not in active working memory...definitely takes a minute. I generally consider them to be almost a code-smell.
- austincheney 5y agoMy answer to this on an interview question is that I deliberately don’t use them and avoid using this because it unnecessarily increases complexity. If that’s a deal breaker then I’ve done us both a favor.
- deleted 5y ago[deleted]
- woutr_be 5y agoHaving done front-end for over 10 years, these interview questions still scare me, because I haven't used any of those in the real world.
- alashley 5y agoLast time I used call, apply and bind was when a co-worker decided to roll his own JS framework. It was as bad as it sounds.
- edflsafoiewq 5y agoYeesh, it's a long article for a simple thing. They just let you control the implicit this arg to a function call. fn.call(t, x, y, ...) calls fn(x, y, ...) with t as this. fn.apply(t, args) is fn.call(t, ...args). fn.bind(t, x, y, ...) is (...args) => fn.call(t, x, y, ..., ...args).
- munchbunny 5y agoAgreed, this article takes a long time to explain the easy part and slides right past the hard part, which is the mess that is the this argument in general.
- anonytrary 5y ago> Call, Apply, and Bind help keep our code clean. In my experience, the exact opposite is true. Modern JS is much cleaner with arrow functions and classes. If I see things like apply/call/bind now, I immediately think it's a smell left over from the ES5 days. I think there are still use-cases in library code, but I pretty much never want to see that in application code.
- deleted 5y ago[deleted]
- deleted 5y ago[deleted]
- Waterluvian 5y agoThis all makes me remember the bad days of bind and bindAll and such and how I never ever have to think about this problem anymore and it’s just wonderful. Can’t remember the last time I saw a ‘this’. Functional programming forever!
- dimmke 5y agoSeriously. It feels like an entire area of JavaScript I pretty much never wade into anymore.
- SilurianWenlock 5y agoWhy not?
- schwartzworld 5y agoNew features were added to the language that make this sort of stuff unecessary.
- cunthorpe 5y agoUntechnically speaking: Arrow functions preserve the `this` in their declaration point, whereas regular functions change `this` if they're called as methods. class Foo { thisCouldBeAnything() {return this} thisIsDefinitelyFoo = () => this } new Foo().thisCouldBeAnything.call('use me') // 'use me' new Foo().thisIsDefinitelyFoo.call('unused') // Foo Since the intention is often to keep `this` to refer to the class, people have been binding every single method in the class constructor, but this is no longer necessary thanks to arrow functions.
- cmpolis 5y agoThis sparked my memory of using "var self = this;" and "var _this = this;" Can't put my finger on it, but _this_ seems quite funny looking back on it (not to say it didn't work...)
- quickthrower2 5y ago
- crazygringo 5y agoOof. These days, there are really only two uses cases I see for things like call and apply. First, you're building some fancy clever JavaScript library (as opposed to regular nuts-and-bolts programming). Then sure, you know what you're doing. Or second, you've got some really funky array/arguments processing that they either enable, or enable you to do in 1 line instead of 5. But for the love of god, avoid this if you can, and if you absolutely must, then please put it in its own descriptively-named function and/or write a helpful comment explaining it too! Usually, most people reading your code base will have no idea what the line does...
- hmwhy 5y agoMaybe an unpopular opinion, but it feels like there is a new trend in celebrating this type of articles that does nothing but repeat already-known concepts without references, adds nothing new, and basically just there to pollute search engine results in the hope of self-promotion. If MDN Web Docs are not enough, there are better sources like YDKJS.
- tengbretson 5y agoThis is why I can't stand browsing any of the "programming" or "JavaScript" sections on Medium anymore. We get it already. You learned how promises or fp work. Move on.
- strbean 5y agoAgreed, the only surprising thing is that this isn't a Medium post and (from glancing at the comments but not the article) it isn't glaringly wrong.
- hn_throwaway_99 5y agoIt is a Medium post.
- mromanuk 5y ago> adds nothing new, and basically just there to pollute search engine results in the hope of self-promotion. The web is big, messy and free (as in free speech), there is not a corpus of carefully written articles trying to extend human knowledge or something. It’s search engine fault for letting SEO (which feels like Spam Engine Optimization) pouring and making it into the index of supposedly relevant information based on your query.
- ojosilva 5y agoBe aware there's a mistake in the examples of using bind() as a currying helper. const multiply = (a) => (b) => a * b ... const multiplyByTwo = multiply.bind(this, 2) multiplyByTwo(4); // returns 8 It does not return 8, it returns a function that returns a function that multiplies by 2. So multiplyByTwo()(4) would return 8. The whole idea of currying with bind() is flawed, as it requires the programmer to think about `this` and can prefill many arguments at once, when currying is, in its essence, about breaking 1 function with N arguments into N functions with 1 argument. There are more problems with other examples. The use of call() in the following snippet is also questionable: const calc = { multiply: function (a, b) { return a * b; }, multiplyMany: function (...args) { return [].reduce.call(args, this.multiply)} }; multiplyMany could just be written as `return args.reduce(this.multiply)`. Why set the context twice with the empty array `[]` and then call(args,...)? The rest of the article has plenty of unnatural or retorted demonstrations of call(), apply() and bind() that are neither illustrative nor educational.