paint-brush
Everything You Need to Know About “this” in JavaScriptby@anmshpndy
900 reads
900 reads

Everything You Need to Know About “this” in JavaScript

by Animesh PandeyMarch 11th, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

An in-depth explanation of the deceptively simple concepts around "this" and its binding (implicit, explicit, and new) in JavaScript.
featured image - Everything You Need to Know About “this” in JavaScript
Animesh Pandey HackerNoon profile picture

What’s “this”?

In the simplest of terms, the JavaScript keyword

this
refers to the object it belongs to on runtime, depending upon its call-site (where it is called).

However, understanding what it would refer to in any given context, requires a slightly deeper understanding of some relevant concepts, which will be covered in this article.

Just to start with,

this
can have the following values depending upon where it is accessed :

1. By default :

this
refers to the global object.

2. Inside a function :

this
refers to the global object. In
strict
mode, however,
this
will be
undefined
.

3. Inside a method :

this
refers to the owner object. (A method is a function that belongs inside an object. In other words, it’s a function that’s an object’s property.)

4. In an event :

this
refers to the element on which the event was triggered.

5. Inside an Immediately Invoked Function Expression (IIFE) :

this
refers to the global object. In strict mode,
this
will be
undefined
, just like any other function in a global context.

6. Inside a Fat-Arrow function : When a fat arrow

()=>
is used to define a function, it doesn’t create a new value for
this
, instead, it keeps referring to the same object it was referring to outside of the function.

This article hopes to give you an understanding of how these values are assigned to this, and how this knowledge can be utilized to suit our requirements.

Call-Site and Call-Stack

As discussed in the last section, we got to know that this is a runtime-binding made for each function invocation, which entirely depends upon where exactly it was called.

This location in the code where the concerned function was called, is called the call-site. An understanding of determining the call-site is crucial towards understanding what this would be bound to, at any given point of the execution.

While finding the call-site is generally as simple as locating where a function was called from, it might not always be that clear because of certain coding patterns that might obscure it.

Hence, it’s important to think about the call-stack, the stack of functions that have been called to get us to the current stage of the execution which we’re concerned with.

Let us take a simple example to illustrate how a call-stack and call-site could be determined.

By following the chain of function calls in order, you can determine the call-stack and call-sites.

* Tip for determining call-stack

Utilize the built-in JS

debugger
provided with any modern browser’s developer tools.

In the execution of any JS code, you can set a breakpoint by using the keyword

debugger
, to stop the execution at that point in the browser.

Let’s say, we add a breakpoint when

thunderbolt()
was called.

The

debugger
stops the execution at the custom breakpoint, and the function call-stack at that point can be viewed on the right side.

In the image above, we can see that the execution was stopped at the point where we mentioned the

debugger
keyword, as soon as
thunderbolt()
is called. At this point, we will not observe any execution of code that comes after the
debugger
(just the
thunderbolt()
log, in this case).

Our primary point of interest right now, is the call-stack which is clearly illustrated on the right-hand side, same as we determined in the example above.

(anonymous)
at the bottom of the stack, refers to the initial global call to
choosePikachu()
.

Binding rules for “this”

Now that we understand what a call-site and a call-stack is, we can learn about how a call-site determines what

this
will hold during execution.

There are four general rules which apply. First, let’s understand them independently, and then, their order of precedence when multiple rules can apply to the call-site.

1. Default Binding

This is the default catch-all rule, when none others apply. It comes from the most common case of a function invocation, which a standalone function call.

Let’s look at the below example.

The variable

ultraBall
declared in global scope is the same as declaring a property on the global object of the same name.

Inside

getPokemon()
, the reference to this defaults to the global object. Hence, we would see the value of
this.ultraBall
getting logged.

However, if

strict
mode is in effect globally or inside
getPokemon
, the
global
object is not permitted default binding. In that case, we will see the error
TypeError : 'this' is 'undefined'
.

2. Implicit Binding

If the call-site has a context object (if a function is called through an owning or containing object, as its property), implicit binding applies.

The rule states that, when there is a context object for a function reference, it’s that object that should be used for its method calls’

this
binding.

Let’s look at a few examples to illustrate the different cases which can arise.

Since the object

pikachu
is the
this
for the
getBaseSpeed
call,
this.baseSpeed
is synonymous to
pikachu.baseSpeed
.

Let’s look at another example to see how only the top or last level of an Object property reference chain matters to the call-site for implicit

this
binding.

As we can see, the

baseSpeed
value is still
90
. That’s because the call to
getBaseSpeed
is bound to its direct caller,
pikachu
, which serves as its
this
binding. In this context, the
baseSpeed
value is
90
.

Let’s look at a few more examples to show common cases where implicit binding can seem unexpected.

In this example, we have lost our implicit

this
binding to
pikachu
in case of assigning
pikachu.getBaseSpeed
to a different variable
baseSpeedFunction
. Now, for
baseSpeedFunction
, this refers to the global object (default binding takes place). Hence, for the call,
this.baseSpeed
will be
50
.

Now, a more common and not-so-obvious way this loss of implicit binding can occur is when we pass a callback function. Consider the following example :

Once again, inside the callback function executor

executeFunction
, we are effectively passing a reference to
pikachu.getBaseSpeedfunction
. Upon execution,
this
will be bound to the
global
object again (or throw a
TypeError
, if
strict
mode is enabled), instead of
pikachu
.

It’s quite common for function callbacks to lose their

this
binding. Another unexpected outcome can arise when the function we’ve passed our callback to, intentionally alters the
this
for the call. For example, Event handlers in popular JavaScript libraries often modify
this
to point to the
DOM element
that triggered the event.

You are not really in control of how your callback function reference will be executed. So far, you don’t have any way of controlling the call-site to assign the binding you intended. This is where explicit binding comes into play.

3. Explicit Binding

To resolve the unintended loss of

this
with implicit binding, we can explicitly set the value of
this
to a given object for a function call.

There are several in-built methods that can help us achieve explicit binding, like :

The bind() method

bind() is a method of the Function.prototype property. This means bind() can be used by every single function.

The bind() method creates a new function that, when called, has its

this
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

In other words, bind() returns a new function that is hardcoded to call the original function with the

this
context set as specified.

The call() and apply() methods

call() and apply() are also methods of the Function.prototype property, with similar but slightly different usage.

The call() method calls a function with a given

this
value and arguments provided individually.

Whereas, the apply() method calls a function with a given

this
value, and arguments provided as an array (or an array-like object).

Invoking

Pokémon
with explicit binding by
Pokémon.call()
or
Pokémon.apply()
allows us to force its
this
to be the
this
of function
PokémonExtension
.

Also, a noteworthy aspect of the above example is that all instances of

PokémonExtension
will bind their respective
this
to the execution of
Pokémon
within them. Such an explicit binding is also called hard binding.

4. New Binding

In JavaScript, there is really no such thing as “constructor functions”, but rather construction call of functions.

When a function is invoked with new in front of it, otherwise known as a constructor call, the following things are done automatically.

  1. A brand new object is created (aka constructed) out of thin air.
  2. The newly constructed object is
    [[Prototype]]
    -linked. (Out of the scope of this article)
  3. The newly constructed object is set as the
    this
    binding for that function call.
  4. Unless the function returns its own alternate object, the new invoked function call will automatically return the newly constructed object.

All binding rules in action

It should be clear that the default binding is the lowest priority rule of the four.

Let’s compare implicit binding, explicit binding, and new binding with each other.

Implicit versus Explicit

As we saw, the explicit binding of

firstAttempt.catchPokémon
with
secondAttempt
took precedence over its own implicit binding, as it did for the second case as well.

Hence, explicit binding is of higher precedence than implicit binding.

Implicit versus new

So, new binding is more precedent than implicit binding.

Explicit versus new?

new
and
call
or
apply
cannot be used together, so something like
var fourthAttempt = new catchPokémon.call(firstAttempt);
is not allowed to test new binding directly against explicit binding. But, we can still use a hard binding to test the precedence of the two.

attemptBinder
is hard-bound against
firstAttempt
, but
new attemptBinder(“Steelix”) 
did not change
firstAttempt.name
to
"Steelix"
, as we may have expected, but it remained
"Onix"
.

Instead, the hard-bound call to

attemptBinder("Steelix")
is able to be overridden with
new
 . Since
new
was applied, we got the newly created object back, which we named
secondAttempt
 , and we see that
secondAttempt.name
indeed has the value
"Steelix"
.

Thus, the newly created

this
is used, rather than the previously specified hard-binding for
this
. Effectively,
new
is able to override hard-binding.

The primary reason for this behaviour is to create a function that essentially ignores the

this
hard-binding, and presets some or all of the function’s arguments.

Finally, determining “this”

We can summarize the rules for determining this from a function call’s call-site, in their order of precedence.

Here they are :

  1. Is the function called with
    new
     ? If so, this is the newly constructed object (New binding). Example,
    var attempt = new catchPokémon("Pidgey");
  2. Is the function called with
    call
    or
    apply
     , even hidden inside a
    bind
    hard-binding? If so, this is the explicitly specified object (Explicit binding). Example,
    var attempt = catchPokémon.call("Pidgeotto");
  3. Is the function called with a context, otherwise known as an owning or containing object? If so, this is that context object (Implicit binding). Example,
    var attempt = firstAttempt.catchPokémon("Pidgeot");
  4. Otherwise, this defaults to the
    global
    object, or
    undefined
    in
    strict
    mode (Default binding).

Summary

Determining the

this
binding for an executing function requires finding the direct call-site of that function.

Once examined, four rules can be applied to the call site, in this order of precedence.

  1. Called with
    new
    ? Use the newly constructed object.
  2. Called with
    call
    or
    apply
    or
    bind
    ? Use the specified object.
  3. Called with a context object owning the call? Use that context object.
  4. Default :
    undefined
    in
    strict
    mode,
    global
    object otherwise.

Credits

Official documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

You Don’t Know JS: this and Object Prototypes, by Kyle Simpson.

Previously published at https://anmshpndy.com/how-well-do-you-know-this