DEV Community

Edward Kim
Edward Kim

Posted on

All about "this" in JavaScript

The this keyword is often a source of confusion and headache for people just starting to learn JavaScript so I decided to write down my 7 "rules" for how to figure out what this is.

1.) Global Context

By default any function that's declared using the function keyword on the global scope, basically any function not declared within an object or a function called with new in front of it, will have a this of the global context. For browsers, the global context is window and for node the global context is global.

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

printThis(); // this will print window in your browser

So given the code above, just calling the function printThis will always print out window.

2.) To the left, to the left

Once the printThis function is placed on an object and is called through that object, the context, or this, will change to the object it's attached to. The key concept here is that the function needs to be placed on an object AND needs to be called through that object. If either of those are not true, then this rule doesn't apply.

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

const obj = {
  print: printThis
};

obj.print(); // this will print obj
printThis(); // this will still print window

The easiest way to figure out what the context is for any function is to look to the left of the function name and see what it's "attached" to. In the code above, since the print function is "attached" obj, the this when print is invoked will be obj.

When we call printThis, there's nothing to the left of printThis so for now, we can safely assume that the context of printThis will be the global context.

3.) Apply

Using the apply method we can change what the this is for any function. Often this is used to pull out a function that's already attached to an object and call it, just this one time, with a context that's not what it normally would be.

In other words, apply allows us to borrow a function from anywhere and call that function, just this once, with a different context than what it normally would be.

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

const obj = {
  print: printThis
};

const newContext = {
  name: "I'm the context now"
};

obj.print.apply(newContext); // this will print `newContext`

The apply method's first argument is always the new context that we want this function to act in. This will only "apply" for that one line of code. Calling obj.print() on any other line, acts like it normally would.

Apply can take in a second argument and that is always an array. This array should hold, in order, any arguments that you want to send to the print function. I won't touch any more on that particular topic but I do need to highlight that one point, so we can talk about...

4.) Call

Call basically does the exact same thing as apply but the main difference is that instead of passing in an array for the second argument, you can pass in as many arguments as print would normally take.

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

const obj = {
  print: printThis
};

const newContext = {
  name: "I'm the context now"
};

obj.print.call(newContext); // this will print `newContext`

The mnemonic I like to use is apply = array, call = commas. In all the cases so far though, there is some sort of visual indicator of what the context will be on that line of code. If there's none, then you can safely (for now) assume that the context will be the global context. At least, until you see the word...

5.) Bind

Bind works much like Call and Apply but the biggest difference here is that bind doesn't fire the function immediately. What bind will do, is return a copy of the original function but with the new context PERMANENTLY "bound".

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

const obj = {
  print: printThis
};

const newContext = {
  name: "I'm the context now"
};

// newPrint's this will always be 'newContext'
const newPrint = obj.print.bind(newContext);
// will print newContext. Visual indicators be damned.
newPrint();


// even if we put 'newPrint' on an object
// then call it like Rule 2, it will still print 'newContext'
obj.boundPrint = newPrint;
// this will still print 'newContext' instead of 'obj' like we would expect with Rule 2.
obj.boundPrint() 

The original function is left the same. Remember that bind will create a copy of the original function, so that original function gets to stay how it is in terms of context. Unless you created the original function using...

6.) Arrow Functions

The only time bind won't change the context of a function, is if that function was declared using Arrow Functions. I won't go into all the details of arrow functions but one of the core parts to arrow functions is that the this for arrow functions is determined lexiconically. That's just a fancy word for saying, the this is determined by checking what the context was when the arrow function was written.

So far, all the functions we've looked at have been written in the global context which means that whenever you use an arrow function, the context in which it was declared will be the global context or window. Even if the function was declared in an object, the context is still the global context.

// arrow function on the global scope
const arrowFunction = () => {
  console.log(this);
};

arrowFunction(); // will print 'window'


// arrow function in an object
const obj = {
  print: () => { console.log(this); }
};
obj.print() // will print 'window'


// even call/apply/bind can't change the context
const boundPrint = obj.print.bind(obj);
boundPrint(); // will still print window

This seems nice and all but then when does the context of an arrow function ever change?

7.) The New Keyword

The new keyword is special in JavaScript because it can turn a function into a class and with that creates a new "functional context." This is hard to explain, so I'll try to show it in code.

// using new in front of Person will turn this function into a class
function Person(name) {
  // using `new` in front of Person it also creates a new context here
  // so all functions within here will have a `this` of person
  // where person is an instance of Person
  this.name = name;

  const print = () => {
    console.log(this.name); // this will always be an instance of Person
  };

  return {
    print: print
  };
}

// person === instance of the class Person
// Person === a class called Person
const person = new Person('Alice');

// somebody === instance of the class Person
// Person === still the same class Person
const somebody = new Person('Bob');

person.print()   // will print 'Alice'
somebody.print() // will print 'Bob'

// bind can't change the print's context since it's an arrow function
const calvin = { name: 'Calvin' };
const boundPrint = person.print.bind(calvin);
boundPrint(); // this will still print 'Alice'

I won't get too much into OOP but the new keyword is crucial for creating a new "context" and any functions declared within the class will inherit that new "context" as its this. This applies to both arrow functions and function created using the keyword function. This is different from declaring a function within any regular function. The context for both will depend on rules 1 - 5. But once we have a new keyword in front of the outer function, then Rule 7 is in effect.

I know that the rules for this are probably still a bit confusing but I hope that I cleared it up a bit. Also, if you're ever unsure what this is, you could just run through these rules one at a time. Or even better, just console.log what this is. Then refer back to the rules to figure out what just happened.

And that's it. The 7 ways this can be set in JavaScript! Please let me know if I've made any mistakes or if I missed a way this can be set.

Edit:
There are functions in the browser that can change your context (this), such as .addEventListener. In these situations, the context will always be the object .addEventListener is attached to. So, it's best not to use arrow functions here since the context for said arrow function will be bound to where it's written. In most cases, where it's written will not match the object you're attaching this event listener to. I think this is a special case for Rule #2.

Top comments (0)