Everything You Need To Know About The 'this' Variable In JavaScript

in #programming7 years ago

There's many things that can trip up beginners when they first learn JavaScript. The one thing that I most commonly see befuddling JS newbies is the mysterious 'this' variable. Even veterans of other object oriented languages can have problems understanding how 'this' works in JavaScript, as it works very differently than it does in most object orientated languages, such as Java or C++. Fear not, as in this very post I will pull back the shroud of mystery surrounding this fickle variable.

Many languages have a 'this' variable. Java and PHP to name a couple. In such languages the variable is typically only available within class methods. That is a function which is a property of a class, and is being called on an instance of that class. In this case the 'this' variable will always refer to the current instance of the class. In JavaScript the 'this' variable sometimes behaves like this, but other times not. It can seem very confusing to determine where the variable gets it value from in each and every case, but when the true purpose of this variable in JavaScript is revealed, working this out becomes trivial. I'll now explain how the value for 'this' is decided in JavaScript.

It's All About Context

Every function in JavaScript has a context, which is a reference to an object. This is true whether the function is standalone, or a method on a class / object. The value of the 'this' variable is the context. Another way of saying context to make it more clear might be 'execution context'. 'this' is the execution context of a function.

So how do you determine the execution context of a function? To explain this I'll show you various code samples, and show you what and why the context is in each example.

Plain Old Function Calls

function logContext () {
  console.log(this);
}
logContext();

This is a simple function which logs out the current context. If you paste it into Chrome's console it outputs the window object:

If you paste it into Node's REPL you get the NodeJs global variable:

So in this example, just calling a plain old function, the context is the global object. This is the default context. In NodeJS 'this' is the global object, in the browser 'this' is the Window object. The global object is the value of 'this' when we don't somehow specify the context.

Specifying A Context

Paste the following into the browser's console or REPL and run it:

var myObject = { foo: true };
function logContext () {
  console.log(this);
}
logContext.call(myObject);

The result is our 'myObject' variable: { foo: true }. We have tamed the 'this' variable, and specified it precisely. So what's going on here? All functions in JavaScript inherit a function called 'call'. This will call the function it's called on, using the first argument as the context. So the first argument to call becomes the value of 'this' in the target function.

Object Method Context

Now I'm going to show you how context behaves when we call a function on an object.

function Person (id, name) {
  this.id = id;
  this.name = name;
}

Person.prototype.logContext = function () {
  console.log(this);
}

var me = new Person(10, 'Dave');
me.logContext();

If you aren't familiar with object oriented programming in JavaScript, I suggest you go and read my Object Oriented Programming In JavaScript series. The code above produces the following output:

We can see that when we call me.logContext() the context, or value of 'this', is the object instance referenced by the variable 'me'. This is how people coming from many other object oriented languages would expect this to work. 'this' references the the current instance. Very straightforward. Time for me to throw you a little curveball...

function Person (id, name) {
  this.id = id;
  this.name = name;
}

Person.prototype.logContext = function () {
  console.log(this);
}

var me = new Person(10, 'Dave');
document.addEventListener('click', me.logContext);

This code is almost the same as the last code. The difference is that instead of calling me.logContext, we pass this function as a click handler on the document object. This means that the function will get run whenever you click anywhere on the webpage. When you do click, the output is:

The context is now the Document object! This is where JavaScript newbies get really confused. Don't worry, I was just the same once, but the explanation is simple.

To understand this we first have to understand just what the argument we pass to the click handler is. 'me.logContext' is a variable which holds the logContext function. When we call this on the 'me' object by doing me.logContext(), we get the context as 'me' because we called that on an object. If however, we assign a reference to the function to another variable, then call the function using that variable, the link to the 'me' instance is gone. I always like to think of this as detaching the function from it's instance.

A really simple way of showing how an object gets detached from it's instance would be to do:

var f = me.logContext;
f();

In this case we get the Window object logged as the context. This is just the same as if we had called logContext as just a plain old function. This happens because assigning me.logContext to f and calling it via f removes all context, effectively detaching the function.

So why does passing the function to the click handler make the context the document? This is simple. addEventListener will call the function passed as the callback in the context of the event's target. It essentially does something like:

function addEventListener(eventName, callback) {
  // The event happens and is stored in 'event'
  callback.call(event.target);
}

This is basically pseudocode, but we can see that addEventListener will be using our old friend call to set the context of logContext to the target of the event, in this case the Document object.

Bind To The Rescue

The problem just described is something which I have seen being the cause of more bugs then probably anything else in a newbie developer's code. The good news is that the solution is trivial. As well as the call function, all functions have a method called 'bind'. Bind takes a context as the argument, and returns a new function which is the original function bound to the given context. This means the bound version of a function will always have the same context.

Let's see how that works:

var f = me.logContext.bind(me);
f();

When we run this the context logged is now the Person, 'Dave'.

Similarly we can now pass our bound object to addEventListener, safe in the knowledge that Dave will not get detached:

document.addEventListener('click', me.logContext.bind(me));

Now when we click the document the 'Dave' Person is logged as the context.

Whenever you pass a function on an object to another function as an argument, the function will be detached from the object. You must bind it if you want it to retain it's context.

If you've read all of this, pat yourself on the back. You now understand a concept which is fundamental to JavaScript, and one which I've seen many people get wrong.

Reproduced from my blog.

Sort:  

Congratulations @wellpaidgeek! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

You published your First Post
You made your First Vote
You got a First Vote

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

By upvoting this notification, you can help all Steemit users. Learn how here!

Interesting
I will follow you to see your future posts! I give you a vote!

Congratulations @wellpaidgeek! You have received a personal award!

1 Year on Steemit
Click on the badge to view your Board of Honor.

Do not miss the last post from @steemitboard:

The Meet the Steemians Contest is over - Results are coming soon ...

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @wellpaidgeek! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!

Coin Marketplace

STEEM 0.18
TRX 0.16
JST 0.029
BTC 76262.49
ETH 2949.42
USDT 1.00
SBD 2.63