The keyword this (italicized in the article for readability) is an important concept for a JavaScript developer to grasp. For many its value remain mysterious until code runs. But there are rules you can follow to be able to say what this is.

What is this?

This refers to an object. Which object? That depends on the way this is being used. Usually it depends on how you invoke a function containing this but that behavior sometimes can be suppressed, also it is possible to use this outside of a function.

Finding this

As mentioned many rules determine the value of this. Some rules take priority over others. This article provides a list of numbered concepts useful for finding this going from the most often (not necessarily always) stronger rules (lower numbers) to the weaker ones (higher numbers). Some examples may need prior knowledge of the later rules so it could also be useful to read it from the other side or reread the article for full knowledge.

1. Function containing this is being called with the new keyword.

Obviously it doesn’t work with arrow functions that cannot be called with new keyword but for readability of the article rules that cannot be compared by priority or most often are stronger are separated. So let’s say that it is the strongest rule determining the value of this.

Example

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

const bound = logThis.bind('thisValue');

bound(); // binding works – logs String {"thisValue"}
new bound(); // newly created object is this

Here we can see that the function called with new keyword did set this to be the newly created object even though it was explicitly bound with bind.

2. Is this being used in an arrow function?

The arrow function cannot be called with new keyword (it also cannot be bound* and ignores strict mode rules regarding this)

Arrow function doesn’t have this. Instead this look up in an arrow function follow usual variable look up rules. It takes this from the enclosing scope and if this is not found there, follow the scope rules and go to the outer scopes. Here you should examine how and where the function is defined and not the call-site.

* Binding doesn’t change this value but you can still prepend arguments using bind. Same applies to a function called with the new keyword.

Example

const obj = {
  importantItem: 'importantValue',
  
  logImportantItem() {
    setTimeout(() => {
      console.log(this.importantItem);
    }, 0);
  }
}

obj.logImportantItem(); // logs importantValue

Arrow functions were introduced in ES6, the absence of this were one of the main reasons for that. Previous example show one of the common use cases for arrow functions. The this of the obj.logImportantItem() call takes its value from the object containing the call site (rule 6). What about the anonymous function passed as an argument to setTimeout? If it was a regular function it would follow rule 7 and this could be anything but the obj. Arrow function uses this of the enclosing scope, in this case example function’s local scope where this is obj and allows us to use that importantItem.

3. A function is explicitly bound with Function.prototype.bind()

The bind() function takes a function and the desired this value as arguments and returns a function with bound this.

Example

const obj = {
  logThis() {
    console.log(this);
  }
};

obj.logThis.bind('newThis')();
obj.logThis.bind('newThis').call('callThisValue');

The example logs String {“newThis”} both times even though context object is provided both times (rule 6) and the second time function is called with call() (rule 5). The bind() takes priority.

You cannot reapply this binding a second time for an already bound with bind() function.

Example

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

const bound = logThis.bind('firstBind');
bound.bind('secondBind')(); // logs String {"firstBind"}

We log this value from the first bind().

4. This is provided as an argument in a built-in function.

Many built-in or third party functions allow us to provide this value to a function as an argument. Usually that value is explicitly bound.

Example

const obj = {
  logThis() {
    console.log(this);
  }
};

[1].forEach(obj.logThis, 'thisValue'); // logs String {"thisValue"} 

forEach is one of these functions. The example logs String {“thisValue”} even though context object is provided (rule 6).

Updated example

const obj = {
  logThis() {
    console.log(this);
  }
};

[1].forEach(obj.logThis.bind('thisValueBoundFirst'), 'thisValue'); // logs String {"thisValueBoundFirst"}

But it does not override explicit binding made before so here we log the value bound with bind().

This rule should be taken with a precaution. It is not always obvious that a certain function works like that but that behavior is very common. Some working examples:

  • Array.prototype.forEach()
  • Array.prototype.map()
  • Array.prototype.filter()

5. A function is called with Function.prototype.call() or Function.prototype.apply()

Function’s call() and apply() methods allow us to call them while setting the value of this. These methods have no difference regarding this behavior only the way to provide further arguments differ (apply() uses an array for that while call() uses separate arguments)

Example

const obj = {
  logThis() {
    console.log(this);
  }
};

obj.logThis.call('newThis'); // logs String {"newThis"}

A function called with call() logs String {“newThis”} (same would work with apply)

It is important to point that the level of binding is important.

Example

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

function myBind(func, thisValue) {
  return function() {
    return func.call(thisValue);
  }
}

const myBoundLogFunction = myBind(logThis, 'thisInFirstCall');

myBoundLogFunction();
myBoundLogFunction.call('thisInSecondCall');
myBoundLogFunction.bind('thisInBind')();

Here using call() we make a function that acts similar to the bind() function. All calls log String {“thisInFirstCall”}) because this value is bound first with myBind(). This way we can override even bind() as seen in the third log in the example

6. Call site is contained in an object

We make a method call from an object. The this is that object.

Example

const obj = {
  logThis() {
    console.log(this);
  }
}

obj.logThis();

this is obj object.

It doesn’t matter if the function referenced in the object is declared outside of it, the call matters.

Example

function logThis() {
  console.log(this);
}
  
const obj = {
  logThis: logThis
}

obj.logThis();

this is still obj.

If we have a property reference chain, top level property reference matter here.

Example

const obj = {
  logThis() {
    console.log(this);
  }
}

const obj2 = {
  obj: obj
}

obj2.obj.logThis();

this is still obj.

7. Default behavior in a regular function

What is this if no rules mentioned before apply?

  • In the global execution context (outside of a function) this refers to the global object.
  • Example 🙂

    console.log(this); 
    

    In the browser we log Window object which is the global object there

  • Inside a regular function without strict mode in the function declaration this is the global object.
  • function logThis() {
      console.log(this);
    }
    
    logThis();
    

    Same as before, we log Window in the browser.

  • Inside a regular function using a strict mode in the function declaration this is undefined.
  • Example

    function logThis() {
      'use strict';
      console.log(this);
    }
    
    logThis();
    

    We log undefined.

    DOM event handlers

    You can test given concepts in DOM event handlers.

    In the inline DOM event handler (i.e. onclick) this is the element with the handler.

    Example 1 (HTML)

    <button onclick='this.style.color = "red"'> Make Me Red</button>
    

    Example 2 (HTML and JavaScript)

    <button id='btn'>Make Me Red</button>
    
    document.querySelector('#btn').onclick = function() {
      this.style.color = 'red'
    };
    

    Both examples show the same thing, the second may appear clearer. Rule 6 is being applied. Text of the button turns red on click because this is the button.

    Let’s check out more examples. We will try different HTML button and event handler combinations. Let’s try to pass some IIFEs to the onclick attribute.

    Example

    <button onclick='(() => console.log(this) )()' >Arrow function IIFE</button>
    

    We log the button here, arrow function takes this from the enclosing scope (rule 2) where this is the button element.

    Example

    <button onclick='(function() { console.log(this) })()' >Regular function IIFE</button>
    

    Here we log Window object, rule 7 is applied.

    Example

    <button onclick='(function(item) { console.log(item) })(this)' >Regular function IIFE with argument this</button>
    

    Here we are passing this from inline event handler as an argument to a regular function and log it, so we log button element here (Rule 6).

    Let’s say we have a function declaration in our JS code.

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

    Example

    <button onclick='(function() { logThis() })()'>function call in regular IIFE</button>
    

    We call our function from JS file, here we log Window object, rule 7 is applied.

    Updated example

    <button onclick='(function() { new logThis() })()'>function call in regular IIFE</button>
    

    Now we call our function from JS file with the new keyword, here we log newly created object, rule 1 is applied.

    Note

    If you add event listener to an element using addEventListener method, regular function passed to the method takes the element with the listener as this.

    Example

    document.querySelector('#btn').addEventListener('click', function() {
      console.dir(this);
    })
    

    Here #btn element is this.

    Though if you pass an arrow function, it behaves like an arrow function and looks for this in the enclosing scope.

    Summary

    This is tricky to find for many developers but should not be avoided. It helps designing reusable functionality in different contexts and is a useful tool in JavaScript developers arsenal. Hopefully this article helped you increase your knowledge in this and was a fun read.

    Best Wishes, Vladas Končius


    Leave a Reply

    Your email address will not be published.