Call a JavaScript function with an explicit this
By Christophe Porteneuve • Published on 25 February 2020
• 6 min
Cette page est également disponible en français.
Aaaah, this
in JavaScript. It’s not that it is actually hairy, it’s more that hardly anybody bothers to actually learn the core concepts behind that thing; as a result, everybody’s cargo-culting their incorrect mental models from their past languages.
People mostly complain they’re “losing their this
”. An intringuing corollary is that since our functions are not intrinsically bound to a static this
value, we should be able to call any function with an explicit this
of our own choosing. And indeed, in JavaScript, this
is part of the calling contract for a function, just like its arguments. Which opens a wide array of cool opportunities.
This is not what it looks like
With one single exception, JavaScript does not automatically define this
when calling a function. Actually, in strict mode and barring explicit overrides, except for that one situation I just mentioned, this
will always be undefined
. So there!
It doesn’t matter where the function “comes from,” how and where it was declared, etc. In JavaScript, this
is defined at call time, not at declaration time.
So what is that notable exception? It occurs when a “traditional” function (one declared using the function
keyword or the shorthand method notation) is called in a pattern I like to refer to as “Subject, Verb, Complement:”
- Subject: an object is used to start the expression
- Verb: we index a property on that object, and the property’s value is our function; it doesn’t matter whether we use direct indexing (the
.
operator) or indirect indexing (the[]
operator). - Complement: we immediately call the obtained function, on-the-fly within the expression term (using the
()
operator, surrounding any arguments).
Consider the following code:
const wife = {
name: 'Élodie',
greet() {
return `Hi, my name is ${this.name}!`
},
}
Now let’s say I run this:
wife.greet() // => 'Hi, my name is Élodie!'
Everything’s dandy. If we take that expression apart, we do find:
- A subject:
wife
- A verb:
greet
- A “complement:” the on-the-fly call using
()
In that case and that case only, JavaScript will define (among other things) this
in the context of that call (technically, it adds 4 extra entries to the call’s Function Environment Record), using the subject
as reference. In our particular code, this
will refer to wife
, so that when constructing the text, this.name
will evaluate to wife.name
, hence 'Élodie'
.
“Losing” your this
100% of other cases boil down to referencing the function without immediately calling it, at least not within the same expression term. Possibilities are endless, such as:
// Ouch, doesn't work
setTimeout(wife.greet, 0)
// This b0rks
navigator.plugins.forEach(wife.greet)
// Grmbl!
const f = wife.greet
f()
The most annoying part is when we are in a callback function inside a code context where this
was fine: the callback is, by definition, passed without being called on-the-fly; the mechanism that receives it is the one calling it at the appropriate time. And then kaboom!
const wife = {
name: 'Élodie',
greet(whom) {
return `Hi ${whom}, my name is ${this.name}!`
},
greetAll(people) {
// Here `this` would be alright…
people.forEach(this.greet)
// …but `this.greet` is not called on-the-fly, so once in it,
// `this` will either be the global object (in default loose
// mode) or `undefined` (in preferable strict mode).
},
}
Defining this
: part of the calling contract
When you think about it, in JavaScript, this
is part of the invocation context for the function, its “calling contract,” so to speak, just like its arguments (or the infamous arguments
) or super
.
Does that mean we can call a function and explicitly control its this
? You betcha!
Consider the full signature for Array#forEach
for instance:
forEach(callback[, thisArg])
It seems we can tell forEach
what this
to use when the time comes and it invokes our callback function. Wonderful!
const wife = {
// …
greetAll(people) {
people.forEach(this.greet, this) // Presto! No problemo…
},
}
But how does forEach
achieve that? It only has some identifier that references our callback, without any extra context information. How does it manage to call it with a specific this
besides its arguments?
Defining this
when you know the arguments
It could use one of the methods available on all functions: call
.
So yeah, I did just say that functions have methods. In JavaScript, functions are objects too. They’re instances of Function
, to be precise. So they feature properties, some of which are data (notably name
and length
) and some of which are functions (specifically call
, apply
and bind
). Breathe, it’s all right, it’s fine. You’ll get used to it.
Instead of just writing:
// Traditional call, that doesn’t concern itself with `this`
callback(item, index, items)
…it could instead go with:
callback.call(thisArg, item, index, items)
The call
method on a function allows us to call it by first specifying the this
it should use, then the regular arguments, if any. Note these are passed individually, so you must know their details and number (what we call the function’s arity) ahead of time.
Based on that, a naive implementation of forEach
could look like the following code. In order not to muddle things further in your mind, we won’t write this as a method intended to be called on arrays (this
would be the array, which could add to the confusion), we’ll just make it a regular function that accepts the processed array as its first argument.
function arrayForEach(array, callback, thisArg) {
// I loathe numerical loops and would as soon use a `for…of`
// over `array.entries()`, but let’s stay on topic here.
for (let index = 0, len = array.length; index < len; ++index) {
callback.call(thisArg, array[index], index, array)
}
}
Defining this
when you don’t know the arguments
This is cool, but what if we want to write generic code, that would force this
regardless of the argument list? This is, by the way, something the bind
method of functions does: it produces a wrapper function around the original one, then calls it when requested with all the passed arguments… and a specific this
:
wifeGreet = wife.greet.bind(wife)
wifeGreet('Elliott') // => 'Hi Elliott, my name is Élodie!' — YAY!
How does bind
manage that?
It could use call
’s sister function, named apply
. It applies the function to a list of arguments, a terminology originating in functional programming. The list could actually be an arguments
object, or any array-like for that matter (an array-like is an object with a numerical length
property and numerical properties between 0 and length - 1
). The important thing is, we don’t deal in individual arguments here, so we don’t need to know their number when writing the code.
Example: reimplementing bind
Armed with that knowledge, we could write a naive equivalent to bind
(leaving aside its ability to prefill early arguments, something called partial application), like this:
function bindFx(fx, thisArg) {
return function () {
return fx.apply(thisArg, arguments)
}
}
Let’s say we want to dive deeper and indeed provide partial application, providing initial arguments in our call to bindFx
, after the two existing ones:
function bindFx(fx, thisArg) {
// Take all arguments from position 2 onwards (as the two first
// arguments, `fx` and `thisArg`, are intended for us)
const partialArgs = Array.from(arguments).slice(2)
return function () {
// Concatenate pre-filled arguments with those we get at call time
const args = partialArgs.concat(Array.from(arguments))
return fx.apply(thisArg, args)
}
}
If this puts your brain into knots, don’t sweat it: we veered off-topic a bit here, going from the forcing of this
to playing with arguments
and partial application. This is ancillary and not critical in any way!
What about ES2015 (ES6)?
Well, even since 2015, should we want to call a function with a specific this
, we still need to go with call
or apply
. Yet a few things have changed…
We don’t really need apply
anymore…
ES2015 came with the rest and spread syntaxes, applicable among other places in function signatures and calls. Thanks to these, it doesn’t matter anymore whether we know ahead of time how many arguments we’ll get. Our naive bind
implementation could look like this:
function bindFx(fx, thisArg) {
return function (...args) {
return fx.call(thisArg, ...args)
}
}
The fuller implementation with partial application would get quite a trim-down:
function bindFx(fx, thisArg, ...partialArgs) {
return function (...moreArgs) {
return fx.call(thisArg, ...partialArgs, ...moreArgs)
}
}
Arrow functions have a lexical this
Unlike “traditional” functions, arrow functions at call time do not define extra entries in the Function Environment Record mentioned earlier. This includes this
and arguments
, among others.
The consequence is that, in an arrow function, these identifiers are resolved like any other (say, console
or document
): lexically, that is, walking outwards along the current nested scopes, until we reach the global scope.
In an arrow function, this
is whatever such reference we encounter first walking outwards along our nested scopes, so it’ll be the one from the closest surrounding function that does have a this
. This is particularly handy when using callback functions inside methods, and the callback code needs to still be able to use the relevant object. For instance:
const contribFilter = {
acceptableAuthors: ['Alice', 'Claire', 'Erin', 'Gillian', 'Maxence'],
process(contribs) {
return contribs.filter((contrib) =>
this.acceptableAuthors.includes(contrib.author)
)
},
}
However, beware! Arrow functions remain functions, so you could invoke call
, apply
or bind
on them, except they won’t force the this
, yet won’t raise an error or even so much as emit a warning! Be extra careful when using code that relies on these methods for functions that get passed in, and arrow functions get passed to them.
function forceThis(fx) {
fx.call({ name: 'Mark' })
}
const host = {
name: 'John',
run() {
forceThis(() => console.log(`${this.name} runs`))
},
}
host.run() // => Logs 'John runs', NOT 'Mark runs'
In this code, even if the arrow function is called, within forceThis
, with a call
specifying that this.name
should evaluate to 'Mark'
, the requirement is blissfully ignored and the arrow function keeps using its lexical this
, the one from its container scope: run()
.
As run()
was called in a “subject-verb-complement” fashion with host.run()
, its this
refers to host
, so that this.name
evaluates to 'John'
.
Pfew!
Want to know more?
We released an amazing screencast that dives deep into this topic, with tons of animated diagrams, extra slides and code samples. From core rules to the nitty-gritty of Function Environment Records to classic API overrides and our trio of call
, apply
and bind
, you’ll learn everything about it in a bit under 1hr30mn of video with full transcripts, to watch and rewatch at your own pace!