The This Threat
Ostensibly, JavaScript’s this
keyword is “essential” to writing object-oriented code in the language. It’s supposed to reference “the current object,” like in its syntactic predecessor, Java. In any codebase using prototype
or class
, this
appears everywhere.
It’s time to throw this
into the garbage. The meaning of this
too often differs from “the current object,” priming any code using this
to fail unexpectedly. There is no strategy which adequately resolves the ambiguity of this
. It should be completely eliminated from our vocabularies.
The Problem
Take this classic example:
function Employee (tasks) {
this.tasks = tasks
this.accomplishments = 0
}
Employee.prototype.work = function () {
this.tasks.forEach(function () {
this.accomplishments++
})
}
var employee = new Employee(['this', 'that', 'the other thing'])
employee.work()
console.log(employee.accomplishments) // 0
console.log(window.accomplishments) // NaN
Here, employee.work()
ought to set employee.accomplishments
to 3. However, the code that counts the employee’s accomplishments has a bug…
In Employee.prototype.work
, the this
in the forEach
callback won’t reference the Employee
instance… instead, it will reference the global object! employee.accomplishments
is 0, since it was never incremented. window.accomplishments
is NaN
, because this
was window
(the global object), and the property accomplishments
of window
was undefined
, and undefined++
results in NaN
.
The same issue occurs when passing references to other methods to forEach
:
Employee.prototype.finishWork = function () {
this.accomplishments++
}
Employee.prototype.work = function () {
this.tasks.forEach(this.finishWork)
}
Witnessing examples like these, we can point and laugh as in wat. Experiencing bugs like these during our 9-to-5’s, we can pull our hairs out. Once we’ve had our fill of comedy and tragedy, we can obtain the knowledge necessary to make wise decisions and not repeat past mistakes.
There Is No Keeping this
Without Pain And Suffering
Adult JavaScript programmers grip onto this
like it’s their favorite teddy bear — except this teddy has a malfunctioning voice box that sometimes electrocutes a programmer when he squeezes his imaginary friend too tightly.
Do we cling onto this language feature? Or do we grow up, and realize we’d be happier without it?
Let’s examine what happens when programmers do the “clinging” thing.
Assigning this
To A Variable
this
can be assigned to a variable to maintain its binding lexically:
Employee.prototype.work = function () {
var employee = this
this.tasks.forEach(function () {
employee.accomplishments++
})
}
But what should the variable name be? employee
, or that
, or self
, or…? Should all instances of this
be replaced with the variable, or only the ones that “need it?” Isn’t it annoying to do var employee = this
in a lot of places — perhaps every single damn method in the codebase? There aren’t any tools that will help enforce these coding styles, either.
Binding this
With bind()
Using bind()
, a copy of a function can be created where this
is bound to a certain value.
This solution sucks. You have to remember to call bind()
on every single damn function and method that needs it.
Things start out like this:
Employee.prototype.work = function () {
this.tasks.forEach(function () {
this.accomplishments++
}.bind(this))
}
But end up looking like this:
Employee.prototype.work = function () {
this.tasks.forEach(function () {
this.somethingElse(function () {
this.ohAndThisThing(function () {
this.weCanDoThisAllDay(function () {
…
}.bind(this)) // don’t miss
}.bind(this)) // a single one
}.bind(this)) // or else
}.bind(this)) // have fun debugging
}
Employee.prototype.weCanDoThisAllDay = function () {
this.tryMe(function () {
this.youAskedForIt(function () {
…
}.bind(this))
}.bind(this))
}
Or they start like this:
function Employee () {
…
this.finishWork = this.finishWork.bind(this)
}
But become this:
function Employee () {
…
this.finishWork = this.finishWork.bind(this)
this.somethingElse = this.somethingElse.bind(this)
this.ohAndThisThing = this.ohAndThisThing.bind(this)
this.weCanDoThisAllDay = this.weCanDoThisAllDay.bind(this)
this.tryMe = this.tryMe.bind(this)
this.youAskedForIt = this.youAskedForIt.bind(this)
}
Also, creating copies of functions can make event listeners impossible to detach, as the identities of the function objects will be different:
function Employee () {
…
button.addEventListener('click', this.work.bind(this))
}
Employee.prototype.work = function () {
…
button.removeEventListener('click', this.work) // doesn’t work
button.removeEventListener('click', this.work.bind(this)) // also doesn’t work
}
Using Special Parameters That Bind this
Employee.prototype.work = function () {
this.tasks.forEach(function () {
this.accomplishments++
}, this)
}
Employee.prototype.work = function () {
this.tasks.forEach(this.finishWork, this)
}
Some methods in the standard library have optional parameters which bind this
specially. Since they’re there, people use them; but they’re no general solution, and you have to remember to use them every single damn time, just like with bind()
.
Banking On Strict Mode To Catch Undefined this
If you add a 'use strict'
pragma to the beginning of a program or function, then in cases where this
would be window
, instead it will be undefined
. Therefore, this.accomplishments++
would fail with an error.
'use strict'
…
Employee.prototype.work = function () {
this.tasks.forEach(function () {
this.accomplishments++ // TypeError: Cannot read property
// 'accomplishments' of undefined
})
}
However, this hardly improves the situation, only making errors a little easier to notice, but not doing anything to prevent them.
Relying On Arrow Functions To Bind this
Correctly
Arrow functions bind this
lexically, which in the abstract would be an excellent solution:
Employee.prototype.work = function () {
this.tasks.forEach(() => {
this.accomplishments++
})
}
Unfortunately, many API’s in JavaScript already bind this
to a value which they consider “the current object,” which might make lexical this
bindings the opposite of what’s expected.
For instance, jQuery binds this
specially:
Employee.prototype.work = function () {
$('#checklist li input[type="checkbox"]').each(function () {
$(this).prop('checked', true) // “this” is an HTML element
this.accomplishments++ // “this” is not an Employee
})
}
Arrow functions reverse the usual binding issue:
Employee.prototype.work = function () {
$('#checklist li input[type="checkbox"]').each(() => {
$(this).prop('checked', true) // “this” is not an HTML element
this.accomplishments++ // “this” is an Employee
})
}
There are lots of other libraries that do this, including the DOM itself.
Calling Methods With A Caller
The issue of passing references to methods and losing their intended this
binding can sometimes be resolved by calling the method from within a simple callback:
employee.tasks.forEach(employee.finishWork) // likely fails
employee.tasks.forEach((...args) => employee.finishWork(...args)) // succeeds
However, in functional code, patterns like the above “likely failing” one will work, since objects aren’t involved:
tasks.forEach(finishWork)
It sure would be nice if we could pass around references to methods with confidence.
Don’t Cling To this
The situations I’ve presented should make it obvious that introducing this
into a JavaScript codebase can have a variety of negative consequences. These consequences can range from outbreaks of repetitive workarounds in localities contacting this
, to subtle issues catching a programmer by surprise after long lying dormant, with a nasty debugging experience leaving him loathing past architectural choices.
You might still be forced to use this
, if a library author binds this
specially, or if his library must be extended with class
, and you want to use that library regardless. In those cases, you’ll need to employ some combination of the aforementioned “solutions.” At least after having read this post, you may now be keener to issues involving this
, and be able to avoid them most of the time.
Don’t use this
when you have the choice! Let’s stand strong against the this
threat.
To Be Continued
Ideally, we will change the way we write JavaScript, recognizing that we do not need this
to write object-oriented code in the first place. I explain why and propose a simpler and more reliable pattern for OOP in another blog post. Click here to read it.