Prelude
This is a three part blog that will explain in detail how the reference keyword this
behaves in different contexts of JavaScript. It's meant for beginners and/or developers who are trying to understand contexts in JS coming from different programming languages.
Definition of Terms
One of the most confusing words that I encountered while learning Javascript was the reference keyword this
. I was coming from a pure object-oriented background, i.e Ruby and Java, in which the reference keyword normally just pointed to the enclosing object in which it was declared.
In Javascript:
this
is a reference keyword that points to a certain variable or object based on the execution context (in-memory scope created when code is running) in which it is defined.
this
in Javascript
Contexts(Scopes) in JS are a very vital part of the language. They help define access to values, variables and objects defined in a function, class or a block. Let's take a look at how this
behaves in different contexts of Javascript.
The Global Context
The global context comprises of objects and variables that exist in the global scope. This means that all functions, classes or variables that are defined in the global context are available throughout your application. In web browsers, we have
window
and in Node.js we have global
as the global object. All of these during execution create a global context.
Let's look at an example below(Paste code in any browser console):
var gVariable = "foo";
gVariable == window.gVariable; // returns true
console.log(this); // ?
Question: What would be the output of the console.log(this)
?
Answer: The window object.
Explanation: At this point, we are in the global context hence the keyword this
will point to the global object in the browser. We can further confirm this fact by testing with this.gVariable
on the console and it will return foo
.
this.gVariable // returns foo
Note that we are using
var
simply becauselet
andconst
are used to refer only to contexts bound by a block. A block in Javascript means any piece of code that is written within a set of curly braces for example afor
loop, a function or even a class except in the case of an object literal. At the moment, we have no block, hence usingconst
andlet
will not give you the same results.
const gVariable = "foo";
gVariable == window.gVariable; //returns undefined
The Function Context
In JS functions, this
behaves a little bit differently based on where it is defined.
Question: What will be the output of invoking the following function in your browser?
function testThis() {
console.log(this);
}
Answer: The window object
Explanation: After the function invocation, testThis()
, the value logged out on the browser console is the window object. The function invocation creates an execution context within the global context and hence because this
has not been specifically assigned to refer to any object or variable, it defaults to pointing on to the window object. This debunks one of the common misconceptions that this
will point to the function object that it is defined. Take note because it does not and you might run into some reference errors on your app.
To explain it further, invoking testThis()
after defining it is similar to writing it as follows:
window.testThis();
Here, we observe that before the invocation, we are referencing the window object to access the function testThis
. As a matter of fact, when you expand the window object, you will find a function property with a key of testThis
.
After this observation, we can safely conclude that:
for all functions in JS, if
this
has not been specified to point to any object or variable, it will always result to referring to the global object by default.
Let's look at another example.
Question: What will be the output of the following code?
const student = {
name: "John",
sayHello(){
console.log(`Hello ${this.name}!`)
}
}
student.sayHello();
Answer: (drum rolls...) Hello John!
Explanation: When sayHello
is invoked, it creates a context that exists in the student
object because that's where it is called from. This means that this
will be bound to the student object in which there exists the a property with the key name
. Hence this
is referencing the student object during execution of sayHello
.
There's a pattern here!
What have you noticed from the two examples above?
We can not start to evaluate how this
keyword works until we have a function invocation. During execution, this
gets bound to an object and that's when we can clearly start to observe how it works. From the two examples, we get consistent reference for all the objects at the left of the dot.
window.testThis() // `this` references to window object
student.sayHello() // `this` references to student object
Therefore, it is safe to conclude that:
In the function context,
this
will point to the object at the left of the dot during invocation time.
The Class Context
JavaScript classes were introduced in ES6(ES2015) and behave much like functions hence determining the binding of this
is a little bit easy following the above examples. The class
keyword basically just provides what we call "syntactic sugar" but in real sense what's happening under the hood is basically dealing with functions. See this blog for more details.
Let's look at some examples that will help us understand better before we make a conclusion:
Question: What will be the output of the following code?
// Example 1
class Reference{
constructor(){
this.name = 'John';
}
sayHello(){
const name = 'Peter';
console.log(`Hello ${this.name}!!`);
}
}
new Reference().sayHello();
Answer:(louder drum rolls...) Hello John!
Explanation: In the above example, we can see that this
has been used in the constructor on the variable declaration name
. However, sayHello
function appears to have the same variable name but has no this
keyword. Note also that because sayHello
is a method inside Reference
, we have to create a new instance of the class in order to invoke the method. This pattern is commonly used in many object oriented programming languages such as Java and Ruby.
Once sayHello
has been invoked, the variable const name
is created in the execution context but when it comes to the invocation of the console.log()
function, this
reference keyword is bound to the class simply because as we saw in the above example, JS functions delegate the binding of this
on the object at the left of the dot at invocation time. Now the enclosing object in this case is the instance created by the section new Reference()
on the function invocation.
Let's look at another example:
Question: What would be the output of the following code?
// Example 2
class Reference{
constructor(){
this.name = 'John';
console.log(this);
}
sayHello(name){
this.name = 'Peter';
console.log(`Hello ${this.name}!!`);
}
}
new Reference().sayHello();
Answer:(even louder drum rolls...)
Reference { name: 'John' }
Hello Peter!!
Explanation: There's something interesting is happening here. We see two outputs because we have the first logging done in the constructor and the second one in the function sayHello
. This happens because the constructor is automatically invoked once we have new Reference()
invocation.
Let's look at how this
behaves.
When we have the invocation of sayHello
, the first binding of this
keyword refers to the string "John" because of the constructor function invocation. The invocation of sayHello
replaces that binding with the string "Peter" because the binding refers to the same object that is the class. Classes in JavaScript are a type of a function and hence instantiating new objects from classes behave the same as in functions which to an extend behave like objects as well.
With these two examples, we see the same level of consistency of this
reference keyword referencing the object at the left of the dot. With the above, we can conclude that:
For any given class in JavaScript,
this
will always point to the class in which it is defined.
Conclusion
From the above examples, it's clear that the best determinant for the correct binding of this
is the object at the left of the dot. It might be a function, class or an instance of a class.
In part 2 of this blog, we will get to see how concepts like function binding in JS affect this
in both ES5 and ES6 as well. In part 3 we'll look at how this
behaves in different JS frameworks.
I welcome any feedback and comments. Stay tuned ;)
References
Image by Sydney Rae