Is "this" really that complicated in JavaScript?
Chuck Choi
Posted on April 19, 2021
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'
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 {...}
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 {...}
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
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
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
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
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
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()
, andapply()
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()
, orapply()
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'sthis
-
bind()
,call()
, andapply()
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
Posted on April 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.