DEV Community

Cover image for JavaScript’s “this” Explained By Starting A High School Band
Kevin Kononenko
Kevin Kononenko

Posted on

JavaScript’s “this” Explained By Starting A High School Band

If you have ever been in a band, had a friend that started a band or seen a corny 80s movie about starting a band, then you can understand the concept of “this” in JavaScript.

When you are reading over some JavaScript, and you come across the this keyword, the steps you need to take in order to figure out its value seem obvious.

You might be thinking, “I just need to find the function that contains this, and then I will know what it is referring to!”

let band= {
  name: "myBand",
  playGig:function() { 
    console.log("Please welcome to the stage" + this.name); 
  }
}
Enter fullscreen mode Exit fullscreen mode

In the example above, for example, this.name refers to the name “myBand”. This seems easy!

But, as you learn more JavaScript concepts, like closures and callbacks, you will quickly find that this does not behave like you would expect.

So, I wanted to create a visual explanation of how this works in JavaScript. Here’s the scenario: You are back in high school, and starting a band with your friends (or maybe you are currently in high school?)

  • Your band has 4 members
  • You play three types of gigs- you play at bars, school competitions and public events in town.
  • Your team can play all types of music, so you try to choose the right songs to match the audience. You don’t want curse words or sexual references at the family-friendly events, for example.

As you will soon see, the biggest concept you need to understand with this is execution context. That is what determines the value of this.

Before you use this tutorial, you need to understand objects and variables. Check out my tutorials on each of those subjects if you need to review.

If you are interested in a more technical version of this tutorial, check out the guide from JavaScriptIsSexy.

The Global Execution Context

Let’s say that your band needs to do a family-friendly gig at the local park or as part of a local fair. You need to choose the right type of music that will keep parents happy and also not offend anyone.

Let’s say that you choose to play some Billy Joel (a famous American artist), and even though this is not your favorite, you know that it’s what you need to do to get paid.

Here is what that looks like in code.

//The songs you will play
var artist= "Billy Joel"; 

function playGig(){ 

  //instruments that your band will use 
  let instruments= ["piano", "microphone", "acousticGuitar", "harmonica"]; 

  console.log("We are going to be playing music from " + this.artist + "tonight!");
} 

playGig();
Enter fullscreen mode Exit fullscreen mode

billyjoelinstruments.png

In the example above, we have an artist variable that indicates what type of music we will be playing. And we have an array full of instruments that will be used to play that music within the playGig function.

In the last line, we call the playGig function. So what is this.artist, in this case?

Well, first we must determine the execution context for this function. The execution context is determined by the object that the function is called upon.

In this case, there is no object listed, so that means that the function is called on the window object. It could also be called like this:

window.playGig() 
// "We are going to be playing music from Billy Joel tonight!"
Enter fullscreen mode Exit fullscreen mode

This is the global execution context. The function is called at the level of the global object, window. And, the variable artist is available as a property of the window object (see this note on the JavaScript specification).

So, in line 1 of the snippet above, we are also saying:

//old version- var artist = "Billy Joel";
this.artist="Billy Joel";
Enter fullscreen mode Exit fullscreen mode

windowexecution

Your band is executing the gig on the global context by playing music that appeals to everyone (unless there are any Billy Joel haters out there).

barpic

Object-Level Execution Context

Let’s say that your band got a gig at a local bar. This is great! Now, you don’t need to play music that satisfies everyone in town. You only need to play music that people can dance to.

barvglobal

Let’s say that you choose Coldplay, since most of their recent songs are pop music. You need a piano, microphone, drumset and guitar for this gig.

Let’s create a bar object with the same pattern as we created for the public park gig.

//The songs you will play in the public park/fair
var artist= "Billy Joel"; 

function playGig(){ 
  //instruments that your band will use 
  let instruments= ["piano", "microphone", "acousticGuitar", "harmonica"];

  console.log("We are going to be playing music from " + this.artist + "tonight!");
} 

//NEW PART 

let bar = { 
  artist:"coldplay", 
  playGig: function(){ 
    //instruments that your band will use 
    let instruments= ["piano", "microphone", "guitar", "drumset"]; 

    console.log("We are going to be playing music from " + this.artist + "tonight!"); 
  }
}
Enter fullscreen mode Exit fullscreen mode

Here is the diagram for the code above:

barobjdiagram

So, let’s say that we want to write the code to get the gig at the bar started. We need to watch our execution context , which is the bar object in this case. Here is what that would look like:

bar.playGig(); 
//"We are going to be playing music from coldplay tonight!"
Enter fullscreen mode Exit fullscreen mode

And, we can still execute the playGig function on a global level, and we will get a different output. This is great news, since we do not want to be playing Billy Joel or Coldplay at the wrong venue…

playGig();
//"We are going to be playing music from Billy Joel tonight!"
Enter fullscreen mode Exit fullscreen mode

So far, this has been the easy stuff. Whenever we have been calling a function, the object that provides the execution context has been pretty straightforward. But that is about to change as we get more complex.

schooljam

Changing Execution Context using jQuery

It’s the big event that has been covered in every single movie from the 1980s: The Battle of The Bands! Yes, every band in your high school is going to get into a competition to see who is the best.

You are going to play some songs from AC/DC, pretty much the coolest band on the planet. But in order to do that, you need a different instrument mix than before:

  • A microphone
  • An electric guitar
  • A bass guitar
  • A drumset

Let’s call this the battle object. Here is what it looks like in code.

let battle = { 
  artist:"acdc", 
  playGig: function(){ 

    //instruments that your band will use 
    let instruments= ["microphone", "electricguitar", "bass", "drumset"]; 

    console.log("We are going to be playing music from " + this.artist + "tonight!"); 
  }
}
Enter fullscreen mode Exit fullscreen mode

Since this is an annual event, we are going to use a click event from jQuery to start your show. Here is what that looks like:

$('#annualBattle').click(battle.playGig);
Enter fullscreen mode Exit fullscreen mode

But if you actually ran this code… it would not work. Your band would forget the words and the notes, then slowly walk off the stage.

To figure out why, let’s return to execution context. We are referencing a DOM element called #annualBattle, so let’s see where that fits within the window object.

DomObjectContext

Since #annualBattle is an element in the DOM, it is part of the document object within the window object. It doesn’t have any property called artist. So if you ran the code, you would get:

$('#annualBattle').click(battle.playGig);
//"We are going to be playing music from undefined tonight!"
Enter fullscreen mode Exit fullscreen mode

In this case, the execution context is an element from the DOM. That is what kicked off the click() method, which used the playGig function as a callback. So, this will end up with an undefined value.

In our analogy, this means that your band showed up to the competition with all your instruments, got in position to play, and then stared at the crowd like they were going to tell you what to do.  It means you have forgotten the context of why you were there in the first place.

To solve this, we need to use the bind() method to make sure that the playGig method still references the battle object, even when we call it from the context of a different object! It looks like this:

$('#annualBattle').click(battle.playGig.bind(battle));
//"We are going to be playing music from acdc tonight!"
Enter fullscreen mode Exit fullscreen mode

Now, we get the correct output, even though the context was a DOM element.

Pulling A Function Out of Context

Let’s say that we wanted to write the code that will allow us to practice for the Battle of the Bands event. We will create a separate variable called practice, and assign the playGig method from the battle object.

var artist= "Billy Joel"; 

function playGig(){ 
  //instruments that your band will use 
  let instruments= ["piano", "microphone", "acousticGuitar", "harmonica"];

  console.log("We are going to be playing music from " + this.artist + "tonight!");
} 

let battle = { 
  artist:"acdc", 
  playGig: function(){ 
    //instruments that your band will use 
    let instruments= ["microphone", "electricguitar", "bass", "drumset"]; 

    console.log("We are going to be playing music from " + this.artist + "tonight!"); 
  }
} 

let practice = battle.playGig; //run a practice
practice();
Enter fullscreen mode Exit fullscreen mode

So you are probably wondering… what is the execution context of the last line?

Well, this will run into a similar problem as the previous example. When we create the practice variable, we are now storing an instance of the playGig method in the global context! It is no longer in the context of the battle object.

practiceGlobalDiagram

If we ran the code above, we would get:

practice(); 
//"We are going to be playing music from Billy Joel tonight!"
Enter fullscreen mode Exit fullscreen mode

Not what we want. We are trying to practice AC/DC, and instead practicing Billy Joel. Yikes.

Instead, we need to use the bind() method just like above. This will allow us to bind the context of the battle object.

let practice = battle.playGig.bind(battle);
practice(); 
//"We are going to be playing music from AC/DC tonight!"
Enter fullscreen mode Exit fullscreen mode

How Anonymous Functions Affect Context

Let’s say that your gig is coming to a close, and you want to give a shoutout to everyone in your band so that the crowd can give each person a round of applause.

In order to do this, we are going to use the forEach() method to iterate through each element in the value of the instruments property. (You will see why we changed it from a variable to a property in a moment). It will look like this:

let battle = { 
  artist:"acdc",
  //instruments that your band will use
  instruments: ["microphone", "electricguitar", "bass", "drumset"], 

  shoutout: function(){ 

    this.instruments.forEach(function(instrument){ 
      console.log("Give a shoutout to my friend for covering the "
+ instrument + " from " + this.artist + "!"); 
    } 
  }
} 

battle.shoutout();
Enter fullscreen mode Exit fullscreen mode

But yet again, if we ran this code, it would not work.

It all centers around the line where we declare an anonymous function to use on each element in instruments. When this function is executed, the first this _will retain the correct context: the _battle object.

But, when we arrive at this.artist in the console.log statement, we will get… “Billy Joel”. This is because of the anonymous function that is used as a callback in the forEach() method. It resets the scope to the global scope.

anoncontext2

In this case, that means we would claim at the end to be playing Billy Joel… d’oh!

But here’s what we can do. We can create a new variable called that to store
this in the correct context. Then, when we reference the artist that we played
in this specific gig, we can reference the stored context, rather than being
forced to return to global context.

    let battle = {
      artist:"acdc",
      //instruments that your band will use 
      instruments: ["microphone", "electricguitar", "bass", "drumset"],

      shoutout: function(){

        //store context of this 
        let that = this;

        this.instruments.forEach(function(instrument){
          console.log("Give a shoutout to my friend for covering the " + instrument + " from " + that.artist + "!");
        } 
      } 
    }

    battle.shoutout();
Enter fullscreen mode Exit fullscreen mode

Get The Latest Tutorials

Did you enjoy this tutorial? You might enjoy the rest of my tutorials on the CodeAnalogies Blog

Top comments (7)

Collapse
 
katzy687 profile image
Natti Katz

Great article!

Also worth mentioning is the use of arrow functions in the last example, which would not reset the scope and allow you to forgo the that=this workaround

Collapse
 
alisonstardust profile image
AlisonStardust

That's exactly what I was thinking.

Collapse
 
kbk0125 profile image
Kevin Kononenko

Very true, Natti. Always tough for me to decide how much to inlcude in one tutorial!

Collapse
 
olisamaduka profile image
olisamaduka

Great tutorial, nicely done, you explain the 'why' very simply; been coding in js for a while now and I still learnt a thing or two.

PS: usually I just go ham and use the es6 fat arrow to deal with my context woes. 😁

Collapse
 
zeerorg profile image
Rishabh Gupta

Greatly explained. Thanks !!

Collapse
 
kbk0125 profile image
Kevin Kononenko

Very welcome!

Collapse
 
dankwansere profile image
Dankwansere

Very nice article, I learned a lot, thanks!