JavaScript (ES5) - this
Martin Himmel
Posted on November 27, 2018
This was originally posted on my site at https://martyhimmel.me on January 16, 2017. Like a number of others on dev.to, I've decided to move my technical blog posts to this site.
The this
keyword can be a bit tricky to understand in JavaScript. Before we get too deeply into it, I should mention strict mode, as the behavior of this
is a little different depending on if it's used in strict mode or not.
At its core, strict mode is there to enforce better coding practices in JavaScript. There are a few things it changes about the way JavaScript code is interpreted by the browser. As the scope of strict mode could easily be it's own tutorial or article (and it has been on many sites!), I'm not going to go over all the details of it here. Instead, I'd encourage you to read Mozilla's developer docs regarding strict mode, especially before continuing this tutorial.
this
in the Global Scope
this
is a reference to an object. What object depends on the context of where this
is called.
In the global scope, both in strict and non-strict modes, this
is a reference to the window
object. Anytime there's a reference to the global scope, it's actually talking about the window
object. Consider this example:
var foo = 42;
console.log(foo); // 42
console.log(window.foo); // 42
console.log(this.foo); // 42
Any variable or function you define in the global scope actually attaches it to the window
object. So, when you're working in the global scope, this
then refers to window
. If you want to see another example of this, open the console and type console.log(window);
, then console.log(this);
- you'll see the same output. And if you create any variables or functions and then run either of those statements, you'll see those variables/functions in the logged object.
this
in an Object
This is the same for both strict and non-strict mode. As seen in the above section, the global scope is actually a top level object - the window
object. That being said, any time this
is called inside an object, it works exactly the same by referencing the object it's called on.
var person = {
firstName: 'John',
lastName: 'Smith',
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
};
console.log(person.fullName()); // John Smith
In the fullName
function, this
is a reference to the container object - person
. this.firstName
could be written as person.firstName
. Why use this
then? Imagine you have another variable with the same name (person
) somewhere else in your script. What does person.firstName
refer to then? Depending on the structure of the code, it may reference the wrong person
object. That's where this
becomes essential - it only references the object it's being called on.
this
in Functions
In the above section, you already saw this
inside of a function, but that function was wrapped in the person
object. But what happens when you have a global function and use this
? This is where strict mode actually matters. Let's look at the code first:
var fullName = 'Jane Doe';
function getName() {
return this.fullName;
}
Let's cover non-strict mode first. In non-strict mode, this
is a reference to the closest object in context. In the previous section, person
was the closest object in the context of the function.
If you remember that the global scope is actually the window
object, then this
in a global function becomes easier to understand. In the fullName
example, the function is in the global scope, which means it's part of the window
object. In turn, the closest object to the function is the window
object, so this
refers to the window
. And since fullName
is the same as window.fullName
(because it's in the global scope), this.fullName
inside the global function references the global variable.
Now let's look at strict mode. In strict mode, this
is a reference to whatever object it was bound to in the execution context. What this means is there's a significant difference between fullName
and window.fullName
. In the former, the execution context is the function, while in the latter, the execution context is window
.
Due to strict mode looking at the execution context rather than object context, when calling getName()
, the function throws an Uncaught TypeError
. The reason being this
is undefined
in the execution context. You can see this if you add a console.log(this);
statement inside the function.
On the other hand, if you call window.getName()
, the function is bound to the window
object at execution time. In that case, the function works properly and if you log this
inside the function, it logs the window
object.
Let's look further into how this
works in functions.
With a constructor type of function, this
works just like it does in objects. We'll use this function as our basis:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getName = function() {
return this.firstName + ' ' + this.lastName;
};
}
Anytime you create a new object with the Person
function, this
is bound to that instance of the object. It works the same way in both strict and non-strict mode.
var person = new Person('John', 'Smith');
console.log(person.firstName); // John
console.log(person.lastName); // Smith
console.log(person.getName()); // John Smith
var anotherPerson = new Person('Jane', 'Doe');
console.log(anotherPerson.firstName); // Jane
console.log(anotherPerson.lastName); // Doe
console.log(anotherPerson.getName()); // Jane Doe
Since this
is bound to the individual instance, person
has its own this
reference, while anotherPerson
has its own reference.
this
in Argument Functions
Things get a bit tricky when you pass a function as an argument to another function, such as in an event listener. Consider a button click listener:
// Using an anonymous function
document.getElementById('myButton').addEventListener('click', function() {
console.log(this); // logs the button element (HTML)
});
// Using a declared function
document.getElementById('myButton').addEventListener('click', myClickListener);
function myClickListener() {
console.log(this); // logs the button element (HTML)
}
It doesn't matter if you create an anonymous function inline or pass a declared function, nor does it matter if you use strict or non-strict mode, the results are the same. In the above button click listener example, this
references the object that called the function - in this case, the button.
That doesn't seem too bad, right? Let's complicate it a bit. What happens if you're passing a function that already has its own this
reference. For example, instead of logging this
to the console when a button is pressed, we want to log the full name of person
(from the previous example).
document.getElementById('myButton').addEventListener('click', function() {
console.log(person.getName()); // John Smith
});
In that anonymous function version, it works the way we expect it to. That makes sense since we're calling the function on the object, not passing it as an argument. Let's use the method as an argument instead.
document.getElementById('myButton').addEventListener('click', person.getName);
// undefined undefined
In this case, even though getName
is a method of the person
object, we're not calling the function directly on the object, but passing it as an argument. Instead of this
referencing the person
object, it references the button element. The button has no firstName
or lastName
property attached to it, so it returns undefined
.
There's a way around that, though. JavaScript has a built in bind
function to handle it. In it's simplest form, the bind
function binds this
to whatever object you pass in.
document.getElementById('myButton').addEventListener('click', person.getName.bind(person));
// John Smith
What that says is to bind this
to the person object when calling person.getName
within the context of the button's event listener.
this
in Closures
Closures have a unique behavior when it comes to this
. Normally, an inner function has access to the outer function's variables. That's not the case with this
. Each function has its own version of this
. Consider this code:
var person = {
scores: [1, 2, 3, 4],
getScores: function() {
console.log(this);
this.scores.forEach(function(score) {
console.log(this);
// do something
});
}
};
person.getScores();
In the getScores
method, this
has predictable behavior - it references the person
object (in both strict and non-strict modes). Things change once we step into the inner function inside the forEach
loop.
The inner function doesn't have access to the object itself - only the wrapping/outer function's variables and anything in the global scope (the window
object). Because of this behavior, you can think about the function as a stand-alone function (from the "this
in Functions" section). In non-strict mode, this
refers to the window
object. In strict mode, this
is undefined
.
So how do we get around that? Create a variable in the outer function that's set to this
so that variable is available to the inner function.
var person = {
scores: [1, 2, 3, 4],
getScores: function() {
console.log(this);
var that = this;
this.scores.forEach(function(score) {
console.log(that);
// do something
});
}
};
Now, the that
variable is assigned to the value of this
in the outer function - in other words, the person
object. Using that
anywhere in the inner function gives us the same behavior as this
in the outer function.
Using var that = this;
or var self = this;
is a common practice to handle this situation. While these are both common, it might be easier to understand if you use a more succinct variable name. In this example, var personObject = this;
makes it clear what you're referring to.
Posted on November 27, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024