Consider the following beauty:

function compute() {
  //
}

Under the assumption that compute is a pure function (a function which will always return the same value given the same arguments. But not only, see Pure Function on Wikipedia), you can easily deduce an interesting thing from those 3 lines: compute will always return the same value.
compute is, basically, a constant.

In JavaScript, it is very easy to break the purity of a function thus breaking the deduction we made earlier about compute being a constant.

One way to break purity, would be to read some external variable:

function compute() {
  return ext.val // ext is some object defined in a parent scope
}

... And that external variable could be this:

function compute() {
  return this.val
}

We now have a way to change the returned value of compute from outside the function.

const a = {
  val: 1,
  compute: compute
}

a.compute() // 1
a.val = 2
a.compute() // 2 ... so long purity, you will be missed

You might be thinking that, at least, with a method call the bound object is never too far from the call, making it explicit that the returned value depends on that object (maybe?)!
Most of the time, is it true. But it is easy to find a counter example:

const ext = { val: 1 }
// create a function bound to `ext`
const compute = function () { return this.val }.bind(ext)

compute() // 1
ext.val = 2
compute() // 2

The misunderstood this

this is probably the most misunderstood concept of the JavaScript language.
People coming from classical Object Oriented Programming languages often mistaken this for an instance of the class the method is in.

A better definition of this in JavaScript would be:

this represents the object which owns the method being called

And, due to the dynamic nature of JavaScript, function ownerships often change at runtime.

Functions are first class citizen in JavaScript, which means you can treat them as any other object: passing them around to functions or returning them.

Due to the asynchronous nature of JavaScript, you actually pass functions around a lot.

fetch("/resource")
  .then(callback) // <- callback is a function

When the functions passed around are actually methods, they lose the binding with their initial object:

const a = {
  callback: function () {
    console.log(this)
  }
}

fetch("/resource")
  .then(a.callback) // <- `this` is undefined

To fix this, you need to manually bind the method to its owning object:

fetch("/resource")
  .then(a.callback.bind(a)) // <- `this` is defined again

this is such a mess for newcomers that TypeScript's documentation has an article dedicated to it.

Functions are about logic, this is about state

So we can ask the question: What does this do for us?

Common response:

this allows us to write logic close to our models. After all, this is what OOP is about.

The way I see it:

this allows us to write stateful functions. It's a way to embed some state into a function

In my opinion, functions should be building blocks that take some arguments and return a result depending on those arguments, and only those arguments.
This is what functional programming is about (see Why Functional Programming Matters), and this is where the front-end is heading (Why Learn Functional Programming in JavaScript? (Composing Software), Master the JavaScript Interview: What is Functional Programming?, Ramda, lodash, Redux ...)

The separation of logic and state

Getting rid of this is as easy as replacing every occurrences of this by a new parameter.

function compute() {
  return this.val + 1
}

// becomes

function compute(a) {
  return a.val + 1
}

The second version of compute has some nice benefits:

  • it is pure (only depends on its inputs)*;
  • its signature explicitly tells that it depends on some input a.

Of course, to remove this from your functions you will also have to change every function calls. Not the funniest part, I agree.

Getting rid of this might feel a little scary and you might think you will lose powerful features. One nice feature I can think of is "method chaining".

const transformed = "test"
  .replace("t", "")
  .replace("e", "j")
  .toUpperCase()

If you remove this from those methods they become simple functions, and you need to transform that piece of code to:

const transformed = toUpperCase(replace("e", "j", replace("t", "", "test")))

Definitely not as sexy as method chaining, right? And that's where functional programming concepts come handy and Ramda (or Lodash) are very powerful allies.

const R = require("ramda")

const removeT  = R.replace("t", "")
const e2j      = R.replace("e", "j")
const testToJS = R.compose(toUpperCase, e2j, removeT)
//    ^ testToJS is a completly new function created
// from the composition of the removeT, e2j and toUpperCase functions

const transformed = testToJS("test")

Note how this last example has make very nice and clear separation between logic and data. Only the last line is dealing with data (read "state") while all the other lines of code are here to describe logic. The separation of concerns is perfectly respected :)

* not using this inside your functions doesn't imply that they are pure at all. You still need to avoid any side effect in order to achieve purity.

Conclusion

Taking away this from your developer's life is a great opportunity for you to leverage the full power of functions and finally start diving into functional programming.

In the process, you will learn how to write very simple functions and compose them together to create fully functional user interfaces.

I personally don't like this and try to avoid it as much as I can.
But, don't get me wrong, this is not evil and most JavaScript developers know how to deal with it.
Sometime you don't really have the luxury of being able to choose to use it or not. If you use React, for example, any stateful component you will write will have to use this (which make sense when you have in mind that this is about state).

That being said, removing this will allow you to:

  • make your function signatures more explicit;
  • isolate your state and models from your logic;
  • avoid any unexpected behavior due to the misunderstanding of the this concept.

Also, it's seems like a perfect excuse to finally learn to love functions and to dive into functional programming :)