Why 'This' in JavaScript
Publikováno: 14.7.2018
While JavaScript is a fun and powerful language, it can be tricky and requires a proper understanding of its underlying principles to mitigate common errors.
In this post, we shall be introd...
While JavaScript is a fun and powerful language, it can be tricky and requires a proper understanding of its underlying principles to mitigate common errors.
In this post, we shall be introducing you to the this
keyword, its behaviour and the hard choices behind it. These will be detailed using appropriate examples to better drive home the point.
What is this?
Developers make the mistake of thinking that this
refers to the “scope” of the function within which it's encountered. This isn’t true because whenever a function is invoked, it runs in a new execution context until its execution is completed. Each execution context usually references an object and the value of that object translates to the value of this
.
For us to properly understand what this
is, we would first need to go over two important concepts:
- Context
- Call-Site
Context
Commonly in the English language, once a noun is defined in a sentence, subsequent references to the subject noun will be done using a pronoun. Understand?
First Statement: Brad is a good boy. Second Statement: He is a very brilliant boy!
Who is a brilliant boy? Brad... obviously!
This is quite the same in JavaScript, the first statement creates the context, while this
functions like 'he' in the second statement and is used to reference the subject in the first statement.
this
refers to the context of an executing function.
This would mean that during every function execution, the focus is on the new execution context which is redetermined whenever a function is invoked, and since the context holds a reference to an object, the value of this
is updated accordingly.
It is impractical to assume that the value of this
is fixed and always refers to the function’s scope, the value of this
is, in fact, dynamic and determined by how a function is called and where it is called:
Different sections in a JavaScript program run in an execution context and maintain that context until there is a switch to a new context. The context of an executing function references the object that invoked it, therefore, a call to this
within a function refers to the invoking object.
Since the context is really important to our understanding of this
, we need to know how/why the context switches during runtime:
How does the context change?
The current execution context in a JavaScript program usually points to the object that invokes a function and can be switched to another object by invoking the function with another object.
Call-Site
The place where a function is invoked in a JavaScript program is called the call-site.
This is also important in determining the value of this
because if the function isn’t invoked by any visible object, the current context of the call-site
would be responsible for setting the new execution context of the function.
In simpler terms, the value of this
represents the context of the initial executing function in a call-site and remains the same in any other call-site an invoking function doesn't provide context.
We can finally define this
based on all of the explanations presented above:
The value of the
this
keyword is dependent on the object upon which it is invoked and not the function itself or the function’s scope.
The call-site
of the function is also important in determining what this
refers to.
This bindings
There are four bindings that are important in determining the value of this
in a JavaScript program. We will look at them in this section.
Default binding
Let’s say we were writing a program and defined a function where we used the this
keyword, then we invoked the function in the global scope (outside of any function) of the program. What object do you think the this
keyword would reference runtime? Let’s see:
let randomFunction = () => {
this.attribute = 'This is a new attribute to be created on the invoking object';
console.log(this);
}
randomFunction();
Good thinking! This
references the global object in the output below:
In the code above, we created a random function that attaches a new property “attribute” to whichever object invokes it. Because the call-site
of the function invocation had its context referencing the global object (this is default), we see that in the output, the console logs a lot of properties that are attached to the global object (Window object) including the property that was created by the random function.
This is the default binding of this
in JavaScript. When a function is called without an object, this
is by default bound to the current execution context, we will look at the other bindings below.
By default,
this
is bound to the global object when invoked by a global function.
It is noteworthy that when we are coding in strict mode, this
holds the value of undefined in global functions and in anonymous functions that are not bound to any object.
Explicit Binding
Remember how we said that the execution context can be changed by invoking a function on an object? Well, there are explicit ways to easily achieve the same result, let’s look at them briefly:
- Bind: This method creates a new function and when invoked sets its 'this' keyword to a provided value (passed as an argument).
- Call - This method calls a function and allows us to specify the context its
this
value should be bound to as an argument. This method accepts optional arguments. - Apply - This function is similar to
call()
with a difference of allowing us to pass in arguments as an array.
Let’s look at an example:
let randomFunction = () => {
console.log(this);
}
let newObj = {
description : "This is a new Object"
}
console.log(randomFunction.bind(newObj)());
console.log(randomFunction.call(newObj));
console.log(randomFunction.apply(newObj));
In the code above, we have explicitly bound this
in the random function to the newObj
variable and we can see that the call
, bind
and apply
methods are available on the Function prototype in JavaScript. They are all correctly bound in the output below:
Implicit Binding
When an object defines a method (that calls this
) as a property and calls that method somewhere in a program, the this
within the method will be implicitly bound to that object. Let’s look at this example:
let newObj = {
description : "This is a new Object",
randomFunction(){
console.log(this);
}
}
newObj.randomFunction();
This is a straightforward example that complies with our definition of this
above; the invoking object is implicitly bound to the method here and it logs the object when invoked:
New Binding
If we have an object constructor and create a new object with it, the new object will have its this
value as a reference to the constructor from which it was created. This is the this
binding that happens when a new object is created using a constructor. Let’s look at an example:
function newObj() {
this.description = "This is an object constructor"
this.randomFunction = () => {
console.log(this);
}
}
let anotherFunction = new newObj()
anotherFunction.randomFunction()
In the snippet above, we defined a new constructor function and gave it description
and randomFunction
property and method respectively. We created an instance with the anotherFunction
variable and invoked its randomFunction
method, here’s the output:
Here, the logged object has the description
property that was defined on the object constructor to prove that this
references the constructor function.
This and That
The takeaway from everything that has been shown above is:
Wherever
this
is defined in a function, it usually isn’t assigned a value until an object invokes the containing function.
You might think that the paragraph above is all you would ever need to refer to when you work with this
but there are scenarios where this
behaves in a weird way (cus JavaScript).
Let’s look at a code snippet where we define a closure that references this
in JavaScript:
let newObj = {
description : "This is a new Object",
randomFunction(){
let a = 1
return function() {
console.log(this);
}
}
}
newObj.randomFunction()();
You would reasonably expect that the closure returns the value of this
as the object that invoked randomFunction
but let’s see what the output says:
Wait, what? How does this
refer to the global object here?
Wasn’t the outer function called by the newObj
object?
Shouldn’t that have switched the execution context and updated the this
reference?
These are all valid questions, however, here’s something to note:
Closures cannot access their outer function’s
this
value by callingthis
within themselves.
This is because the this
value of a function is only accessible by the function within which it is encountered and not its inner functions. Hence the this
of an anonymous closure will be bound to the global object where strict mode is not being used.
How can we get the preferred behavior?
Here's a trick. Consider this code snippet:
var newObj = {
description : "This is a new Object",
randomFunction(){
var that = this;
return function() {
console.log(that);
}
}
}
newObj.randomFunction()();
Here, in randomFunction
we declare a new variable called that
and assign it the value of this
. This way, we can reference the this
value of the outer function within the closure since the scope of an outer function is always accessible by its inner functions.
Here’s the output:
Great! We have referenced the this
value of the outer function in the closure by creating a that
variable.
It should be noted that we could have called the variable anything at all but chose that
because so many JavaScript developers already find it convenient to call it that
. I sometimes choose cat
or thanos
hehe.
Now you know this
... and that
.
Conclusion
We have explored the this
keyword in JavaScript clarifying its behavior and use in the process. We also looked at the scenario of closures where the this
logic may be elusive and we went over a fitting solution, however, there are situations where this
can still prove tricky, the best way to figure it out is to identify which of the bindings is in operation during the function execution and trace back the context carefully. Feel free to leave your feedback and comments. Happy coding!!