JavaScript this keyword

JavaScript this keyword

With students learning programming for the first time this is one of the features that confuses a lot of new programmers, so basically, what is this ?

The this keyword refers to the object, that is executing the current function.

All javascript functions while executing hava a reference to their current execution context, called this.

Remember, if a function is a property of an object, by convention we call it a method.

If a method is being executed, this references the object the method belongs to.

If the function that is being executed is a regular function, this references the global object.

//so in methods this references the object itself
myObject.myFunction ---> this references myObject

//a regular function ---> this references the global object
function  ---> global (this is the window object in browsers, and global object in node)

lets look at a simple object literal, with one property title and a method play()

const music = {
  title: 'a',
  play(){
    console.log(this);   //in our music object let's see where this points to
  }
}      

music.play();

//output
//Object {title: "a", play: function play()}

In the example above, because play() is a method in the music object, this references the music object itself.

With JavaScript's dynamic nature , it's easy to add a method later, so for example, lets add a stop method to our music object.

const music = {
  title: 'a',
  play(){
    console.log(this);
  }
}      

music.play();

music.stop = function() {
  console.log(this);
}

//output

//Object {title: "a", play: function play(), stop: function ()} 
//Object {title: "a", play: function play(), stop: function ()}

code example : 1. Js this keyword

In the example above, because stop() again is a method in the music object, this references the music object itself. Also note that, even though the stop method was added after the music object was declared, when invoking the music.play(); method, in the console.log, the output also shows this newly added stop:function method.


Now lets look at regular functions

Remember: If a function is a regular function, that is it is not part of an object then this references the Global Object. Which is the Window Object in Browsers and Global Object in node.

const music = {
  title: 'a',
  play(){
    console.log(this);
  }
}      

music.play();

//a regular function, not a method of an object
function stop () {
  console.log(this);
}

//output

//Object {title: "a", play: function play()}
//Window {window: Window, self: Window, document: HTMLDocument, name: "", location: Location…}

What about constructor functions?

//Object literal
const music = {
  title: "a",
  play() {
    console.log(this);
  }
};

//constructor function
function Music(title) {
  this.title = title,
  console.log(this);
}

music.play();

//create a new Music object
const m = new Music('b');


//Object {title: "a", play: function play()}
//Music {title: "b", constructor: Object}

When dealing with a regular function, this by default references the Window / Global object. However, when you call a function using the new operator, which is the case of constructor functions, this references the newly created empty object.

With constructor functions, when you use the new keyword, the first thing that happens, is that JavaScrip creates an empty object {} then it sets this in the constructor function to point to the newly created empty object.

So in the above Music constructor function example, this is referencing **m**, our new object, NOT the window object.


Now lets add two more properties to our music object literal, an array called tracks and a method called showTracks(). Inside the showTrack() method, lets use the array.forEach method to iterate through the tracks and log them to the console.

const music = {
        title:'a',
        tracks: ['track-1', 'track-2', 'track-3'],
        play() {
          console.log(this);
        },
        showTracks(){
          //tracks is an array use the array.forEach method and pass 
          //an anonymous function to perform a task on each track item
          this.tracks.forEach(function(track){
            console.log(track);          
          })
        }
      }

music.showTracks();

//track-1 
//track-2 
//track-3

Great, all the tracks are being logged out. Wouldn't it be good to see the title as well. So hat if we want to show the title as well as the tracks, what happens if we do the following:


const music = {
        title:'a',
        tracks: ['track-1', 'track-2', 'track-3'],
        play() {
          console.log(this);
        },
        showTracks(){
          //what if we want to also show the title for each track
          this.tracks.forEach(function(track){
            console.log(this.title, track);          
          })
        }
      }


//undefined "track-1" 
//undefined "track-2" 
//undefined "track-3"

Why do we get undefined? Replace this.title with just this to see what this is referencing! Shouldn't this.title be referencing the music object?

NO!

Here, this.title is inside is a statement inside the anonymous callback function , it is not inside the music object literal. The only methods we have in the music object are the play() and showTracks() method.

So in the snippet below, it is the Global Window object, which is executing the anonymous callback function, hence here this is referencing the Window Object, which does not have a title property, title belongs to the music object.

this.tracks.forEach(function(track){
            console.log(this, track);          
          })

So how can we fix the code, so the title as well as the tracks are shown next to each other?

Here, luckily the JavaScript Array forEach() Method has two parameters.

// Callback function
forEach(callbackFn)
forEach(callbackFn, thisArg)

callback
Function to execute on each element. It accepts between one and three arguments.

thisArg Optional
Value to use as this when executing callback.

Looking at the documentation, you'll see - the first parameter as seen above is our callback function, the second optional parameter is **thisArg** so we can pass an extra argument here, the argument is an object we can pass to the forEach function and this will reference that object.

For example, lets create a simple object literal {test:pedbad} , and pass this object as our second argument,


this.tracks.forEach(function(track){
            console.log(this, track);

          }, { test:'test:pedbad'})

So, lets see what we get in the console, when we invoke the music.showTracks(); method:

  //Object literal
  const music = {
        title:'a',
        tracks: ['track-1', 'track-2', 'track-3'],
        play() {
          console.log(this);
        },
        showTracks(){
          //tracks is an array use the array.forEach method and pass 
          //an anonymous function to perform a task on each track item
          this.tracks.forEach(function(track){
            console.log(this, track);

          }, { test:'pedbad'})
        }
      }

  music.showTracks();

//Object {test: "pedbad"} "track-1" 
//Object {test: "pedbad"} "track-2" 
//Object {test: "pedbad"} "track-3"

In the silly example above, we created an object literal, and passed it as our second argument to our array.forEach() Method

What we want to do is pass this as the second argument!

const music = {
        title:'a',
        tracks: ['track-1', 'track-2', 'track-3'],
        play() {
          console.log(this);
        },
        showTracks(){
          //what if we want to also show the title for each track
          this.tracks.forEach(function(track){
            console.log(this.title, track);          
          }, this) //here this is in the execution context of the showTracks() method 
        }
      }

music.showTracks()

/*
Object {title: "a", tracks: Array[3], play: function play(), showTracks: function showTracks()}
"track-1" 
Object {title: "a", tracks: Array[3], play: function play(), showTracks: function showTracks()}
"track-2" 
Object {title: "a", tracks: Array[3], play: function play(), showTracks: function showTracks()}
"track-3"
*/

see the code examples on my p5.editor here