loading...

JavaScript (ES5) - this

martyhimmel profile image Martin Himmel ・1 min read

Intro To JavaScript (ES5) (10 Part Series)

1) JavaScript (ES5) - Getting Started 2) JavaScript (ES5) Data Types 3 ... 8 3) JavaScript (ES5) - Working With Selectors 4) JavaScript (ES5) Loops 5) JavaScript (ES5) Conditionals 6) JavaScript (ES5) Arrays 7) JavaScript (ES5) Functions - Part 1 8) JavaScript (ES5) Functions - Part 2 9) JavaScript (ES5) Objects 10) JavaScript (ES5) - this

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.

Intro To JavaScript (ES5) (10 Part Series)

1) JavaScript (ES5) - Getting Started 2) JavaScript (ES5) Data Types 3 ... 8 3) JavaScript (ES5) - Working With Selectors 4) JavaScript (ES5) Loops 5) JavaScript (ES5) Conditionals 6) JavaScript (ES5) Arrays 7) JavaScript (ES5) Functions - Part 1 8) JavaScript (ES5) Functions - Part 2 9) JavaScript (ES5) Objects 10) JavaScript (ES5) - this

Posted on Nov 27 '18 by:

martyhimmel profile

Martin Himmel

@martyhimmel

Web dev and aspiring game dev who loves to teach and dabbles in various languages.

Discussion

markdown guide