- Bang their heads against the wall trying to understand Function.prototype.bind, Function.prototype.apply, and Function.prototype.call.
- Pray to the gods they don’t have to use this in their next project.
- Have their request denied by the gods.
And, ES6 classes don’t make things easier, because even though Prototypes make writing factory functions easy and ES6 Classes are sugar over them, they (ES6 classes) are written in a way that makes writing factories difficult. They abstract away (read: obfuscate) the underlying factory pattern and make it more cumbersome to fine-tune constructors by making them special functions instead of just regular functions.
I think this as it is commonly taught and used is an anti-pattern, an anachronistic concept from the days when the OO factions controlled the land, inheritance was king, and the notion that even one part of your code was not as DRY as possible was enough to earn you the scorn of your peers and colleagues. Here are some of my reasons, and this is by no means exhaustive:
this couples your code
this inherently couples your code. Coupling is sometimes inevitable, but when this is proliferate in your codebase, you can bet your code is gonna break left and right when you refactor. You can mitigate this by limiting inheritance and using this as a placeholder in ES6 classes for ret or whatever you tend to name your factory objects returned from factory functions, and completely avoiding extends, but your code is still coupled inside the module in which you’re using this. Here is an example:
This errors because we’ve lost our context inside action2 and action3this is now undefined (in strict mode). We can solve this easily:
NOTE: Some people might argue that this is precisely what allows functions to be passed around since it can be used in different contexts. I think you could just pass what would be the context as a parameter to the function, but even if we accepted that response, which I think does hold some weight, I’m just saying it makes passing the function around fragile.
Oh, and also if you want to use this in different contexts, you start to sacrifice readability for DRYness. Can you imagine how your usage of thismight balloon into a function with high cyclomatic complexity (a bunch of if else statements) based on the context?
this introduces unnecessary mental overhead
this increases the mental overhead of understanding the internals of a class. Take the previous code example with doStuff(): now you need to see why action2 and action3 rely on the context and how they use it. Couldn’t we just avoid all this by passing what this would have been to the function call, or using an object name that will never be lost in context because the function would close over it?
Compare the class example to the following snippet:
No need for special classes, this, and no need for .call() or .bind(). It’s just a simple factory function that returns an object with some stored state and a couple of methods.
Ok so I’m done bashing on this. I think there is a way to use this that avoids many of these pitfalls. Sure, it’s not too difficult to use .bind() (I’m looking at you, React). But let’s not tell ourselves this is anything but a hack. I think this could be a springboard for discussion on how to improve how developers use this. Here are some of my ideas (note: one of them includes just not using this):
Babel / Typescript: Class property methods
If you’re completely wedded to ES6 classes, that’s fine. Some people prefer using ES6 class sugar to the underlying prototypes, and that’s their right. But you can’t deny that classes as they are now make this a nuisance to work with. Luckily, Babel has a transform-class-properties plugin and if you use Typescript, it automatically gives you this ability. What ability you may ask? The ability to write class methods as arrow functions so that they have no lexical scope. In layman’s speak, that just means that they will always preserve their original this when the function is defined:
note how `saySup` does not throw an error here
You can try this out with typescript if you want. ES6 arrow functions have the same benefit as .bind() here, but they actually are extremely different from a bound function. In fact, an arrow function cannot be bound. Instead, it inherits its scope from its outer scope. In this case, that means this will always be the instance of the Person class.
I find myself using this solution frequently with Angular 2 projects because all Angular 2 components are classes. It also allows you to write React while avoiding the this.someFunction = this.someFunction.bind(this) hack in your component classes.
It’s important to remember that under the hood, we’re talking prototypes and not classical inheritance here. My preferred method of constructing objects is to just return them from a function. No inheritance, no this, and no prototypes. With ES6 or an Object.assign polyfill, you gain all the benefits of prototypes too.
Here’s the same example from above with a factory:
Chances are, I didn’t really blow your mind with this snippet. That’s because I wasn’t trying to. We don’t need these whiz bang sugary solutions when we can just use the damn language.
Let’s say you want composable factories. Say no more:
person has methods from both factory functions
What I like about these solutions is that you can create complex hierarchical inheritance schemes if you’re a sadist, and you can avoid it if you don’t like doing that, and you gain all the benefits of both worlds, with none of the harms of classes.
Use `this` if it makes you happy
All I’m saying is this makes me an unhappy developer, so I try to avoid it. In the end, these debates amount to bikeshedding, and if you think it involves less mental overhead to use this than it does to use factories, then you should completely ignore my arguments because they do not apply to you. But if you’ve found yourself grudgingly using this, it’s worth asking yourself if you even need to in the first place.