DEV Community

loading...
Cover image for Is "this" really that complicated in JavaScript?

Is "this" really that complicated in JavaScript?

chuckchoiboi profile image Chuck Choi ・7 min read

this keyword can be one of the most confusing monsters in JavaScript. Especially those who didn't learn JavaScript as their first programming language find it pretty confusing as it behaves differently in JS compared to the other languages. And many programmers rely on libraries like jQuery, so they learn how to use it but don't get to fully comprehend its fundamentals. Well, let me show you how this isn't as confusing as you think.


What is "this" in JavaScript?

MDN Web Doc explains that this is:

A property of an execution context (global, function or eval) that, in non–strict mode, is always a reference to an object and in strict mode can be any value. In most cases, the value of this is determined by how a function is called (runtime binding). It can't be set by assignment during execution, and it may be different each time the function is called.

To put it simply, this is a keyword used to reference the execution context. We could think of it as "whoever it is that calls the function." So this typically refers to the object that is invoking its method. In JavaScript, any value can be determined when it is defined or when the function is called. The latter is the case for this keyword usually in the case of Implicit binding.

With Implicit binding, the value of this is determined based on the execution context. But this behaves differently if the function being invoked as an arrow function or if you are using strict mode introduced in ES5. And there's Explicit binding which you can explicitly bind this keyword to an object you pass as an argument for call(), bind(), and apply() methods. Let's dive in deeper to each of them.


1. Object Method

Take a look at the following code:

  var hi = {
    myName: 'Slim Shady',
    myNameIs: function() {
      console.log(this.myName);
    }
  }

  hi.myNameIs();  // prints 'Slim Shady'
Enter fullscreen mode Exit fullscreen mode

The above code's hi object has a method myNameIs which logs myName of this in the console. When hi object invokes its method like the following hi.myNameIs(), the object who called the function is hi which makes this to be implicitly bound to hi object. Hence, the console will log myName of the object 'Slim Shady'. A simple way to look at it is that whatever is on the left side of a dot notation of a method being invoked is the object that this will be referring to.

How about this example though:

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

  whatIsThis(); // prints Window {...}
Enter fullscreen mode Exit fullscreen mode

Hmm... we just discussed that an easy way to understand this keyword in object method invocation is paying attention to the left side of dot notation. But this time, whatIsThis is a function defined using function declaration, and we can execute it without an object which logs this as the window object.

Sounds pretty confusing right? Well, when we declare a function, we are making it a global function available to the global object, so the function's containing scope is the global object Window. Another way to execute whatIsThis is: window.whatIsThis(). Look at that, window is on the left side of whatIsThis()! This brings me to the next point -- this in global context.


2. Global Context

As we discussed, when a method inside an object is executed by the object, this refers to the object. But what happens if I try to simply log this into console? Give it a try in your browser.

  console.log(this); // prints Window {...}
Enter fullscreen mode Exit fullscreen mode

Looks like this refers to window object. By default, this refers to the global object (Window in browser environment). If we want to understand why it is, ask yourself (or Google) what the window object is. If we take a look at MDN Web Docs again, it explains:

The window object is the Global Object in the Browser. Any Global Variables or Functions can be accessed as properties of the window object.

When we add this to your browser console, your global environment Window is executing the expression this, so the window object is being referred to in this global context.

Back to the Slim Shady example, here's a function created using the myNameIs method in hi object earlier:

  var hi = {
    myName: 'Slim Shady',
    myNameIs: function() {
      console.log(this.myName);
    }
  }

  var hisNameIs = hi.myNameIs;

  hisNameIs(); // prints undefined
Enter fullscreen mode Exit fullscreen mode

Interesting. hisNameIs function logged undefined in the console. Let's try to understand what happened at the line var hisNameIs = hi.myNameIs.

First, hi.myNameIs is a method -- a property containing a function definition. We simply declared a global function named hisNameIs by using var and initialized it with the function definition from hi object passed.

Second, global functions are stored as properties in the window object. When we invoke the global function hisNameIs(), it is the same as window.hisNameIs(). The window is the object that is executing its method hisNameIs, so this is now referring to the window object. window object does not have a property named myName, so it will return undefined.

In conclusion, this will refer to the global object in global context.


3. Strict Mode

JavaScript was first introduced in 1995 as Mocha which took 10 days to develop by a Netscape programmer named Brandon Eich. It would be surprising if the language came out to be perfect in 10 days of development right? The language has evolved to today's version 6 (ES6), with the language designers' attempt to correct the flaws in the past versions. Its legacy features were not possible to be removed in order to maintain the backward compatibility, which is why strict mode was introduced in ES5 to opt in to correct the early language flaws.

this keyword is one of them. It behaves differently when you opt into strict mode:

  function whatIsThis() {
    "use strict";
    console.log(this);
  }

  whatIsThis(); // prints undefined
Enter fullscreen mode Exit fullscreen mode

In strict mode, this keyword will default to undefined in function invocation. It is likely that this keyword was not meant to point to the window object, as you can simply use window keyword to do so.

In ES5, bind() method was introduced to explicitly set the function's this regardless of how it is called. You can pass an object as an argument when using bind() method, and the function's this keyword will refer to the object no matter how the function is invoked. Bringing back the code from earlier using bind() method this time, we can now create a new function with object passed explicitly like this:

  var hi = {
    myName: 'Slim Shady',
    myNameIs: function() {
      "use strict"
      console.log(this.myName);
    }
  }

  var hisNameIs = hi.myNameIs.bind(hi)

  hisNameIs(); // prints Slim Shady
Enter fullscreen mode Exit fullscreen mode

Boom! Even with the strict mode, hisNameIs function's this will refer to the hi object passed no matter what. call() and apply() are basically the same which you can pass additional arguments to the function. The three methods are slightly different which you can read more about in this blog post.


4. Arrow function

this inside an arrow function behaves a bit differently compared to the one inside a function declaration or a function expression. Arrow function was introduced in ES6 as an alternative to a traditional way of defining function. Let's compare these two objects using the different versions of function:

// using regular function as callback inside forEach()
var oldPhone = {
    owner: 'Chuck',
    apps: ['Facebook', 'YouTube', 'Uber'],
    useApps: function () {
        this.apps.forEach(function(app) {
            console.log(this.owner + ' is using ' + app)
                        // this refers to the window object
        })
    }
}

oldPhone.useApps()
// prints undefined is using Facebook
// prints undefined is using YouTube
// prints undefined is using Uber

Enter fullscreen mode Exit fullscreen mode

oldphone.useApps function iterates each of the apps using forEach with a regular function passed as a callback function. However, the callback function inside forEach method does not bind to the original object. Instead, it will bind to the global window object thus this.owner returns undefined.

This could be very inconvenient if we were doing something similar as a class. There are two ways to fix it though, forEach() method takes an optional argument thisArg in addition to the callback function like this: arr.forEach(callback[, thisArg]).

Or we can use an arrow function as a callback to utilize its lexical scoping:

// using arrow function as callback inside forEach()
var newPhone = {
    owner: 'Chuck',
    apps: ['Facebook', 'YouTube', 'Uber'],
    useApps: function () {
        this.apps.forEach((app) => {
            console.log(this.owner + ' is using ' + app)
        })
    }
}

newPhone.useApps()
// prints Chuck is using Facebook
// prints Chuck is using YouTube
// prints Chuck is using Uber
Enter fullscreen mode Exit fullscreen mode

Voila! This time the callback function's this referred to newPhone, and logged this.owner as 'Chuck'. Arrow function allows you to write functions in a cleaner way, and they have lexical scope I mentioned earlier which means that they will inherit the scope from its parent.

The callback function nested inside the forEach method above inherited the scope from its parent useApps which is newPhone object. Because of this nature, The value of this inside an arrow function is determined when that arrow function is defined unlike the typical situations from earlier. I personally think that this inside an arrow function is the most confusing part of this keyword, but it simply inherits the scope from its parent.

NOTE
bind(), call(), and apply() are not compatible with arrow functions. Arrow functions will inherit the scope from its parent regardless.


Conclusion

To conclude, let's summarize how this works in JavaScript:

  • this is a keyword used to reference the execution context
  • In method invocation, the object that is invoking the method would be the execution context this will refer to
  • In global context like regular function invocation, this will default to the global object
  • In strict mode, this keyword will default to undefined in regular function invocation.
  • You can use bind(), call(), or apply() to explicitly bind an object to a function
  • An arrow function will inherit the scope from its parent, so this inside an arrow function will follow its parent's this
  • bind(), call(), and apply() do not work for arrow functions

Hope this was a helpful resource for you to understand how this works in JavaScript. Feel free to comment below if you have any questions or notice any inaccurate information and I will respond as soon as possible :)

Follow me on Clubhouse @chuckchoiboi

Discussion (10)

pic
Editor guide
Collapse
taufik_nurrohman profile image
Taufik Nurrohman

You can also manipulate this context:

console.log(this); // `Window`

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

onClick(); // `Window`

button.addEventListener('click', onClick);
button.click(); // `button`

onClick.bind(new F1);
onClick(arg1, arg2); // `F1`

onClick.call(new F2, arg1, arg2); // `F2`
onClick.apply(new F3, [arg1, arg2]); // `F3`
Enter fullscreen mode Exit fullscreen mode
Collapse
bendman profile image
Ben Duncan • Edited

To summarize: unless someone changes it using call/apply/bind or you are inside of an arrow function, this refers to whatever is left of the last dot at the point of invocation.

button.click(); // this === button
whatIsThis(); // this === undefined (or the global/window object in non-strict context, because whatIsThis === window.whatIsThis)
newPhone.useApps() // this === newPhone
Enter fullscreen mode Exit fullscreen mode
Collapse
jesterxl profile image
Jesse Warden • Edited

Is "this" really that complicated in JavaScript?

Yes. Easier to just not use.

Collapse
codyseibert profile image
Cody Seibert

When you are inside a function invoked by a dom event, such as clicking a button, this refers to the element clicked. Or invoking a function that was created with a "bind" somewhere else in your codebase, you have no clue what this might equal. Yeah, so this keyword can be pretty confusing because its value can be so arbitrary at times. Thanks for the article!

Collapse
tilkinsc profile image
Cody Tilkins

Come from Lua, this isn't too confusing. However, it baffles me why they thought it was a good idea to change the this paradigm between arrow and normal function declaration. I've noticed even React will warn about the differences in docs.

Collapse
chuckchoiboi profile image
Chuck Choi Author

@tilkin Agree with you that the whole part isn't that confusing until you come across the arrow functions. It's just completely different from the typhttps://assets.grammarly.com/emoji/v1/1f642.svgical this keyword's pattern. But because of its lexical scoping, arrow functions are perfect to use for callback functions inside higher-order functions to inherit its parent scope. But at the same time, would've been easier to just utilize the explicit binding haha!

Collapse
tatianacodes profile image
Tatiana

this really cleared it up for me! It has consistently tripped me up in interviews. And sick location, howdy from Irvine, neighbor!

Collapse
chuckchoiboi profile image
Chuck Choi Author

Howdy @tatianacodes ! Great to hear OC presence in the Dev community :)

Collapse
benhammondmusic profile image
Ben Hammond

Dude! Crushing it!!! ‘this’ was awesome!

Collapse
chuckchoiboi profile image
Chuck Choi Author

Haha thank you for reading this article!