Considered one of the quirkiest things in javascript is simple as hell. This article tries to elucidate this
and clear out it’s elusive patches that I encountered during a recent face-off with javascript.
Prerequisites: Just shelve your understanding of classical Object Oriented
this
.
Concepts
Before diving in, let’s be clear on the basics. In Javascript
- Everything is an object and functions are no exception with name, properties and code libs.
- Each and every code runs inside contexts which are stacked up in context stack of JS Engine.
- The global execution context(window object in the browser and global object in nodejs) resides at the bottom of the stack and stays until the code terminates.
- Whenever a function is invoked(and not defined) a context is created and is pushed to the top of the existing stack, popping it at the end of it’s execution.
- Every context consists of a variable environment, lexical(outer) environment and a special variable
this
, wherethis
refers to the value of the object that invoked the function. - Reference of
this
inside a function is independent of it’s lexical environment i.e. the value ofthis
inside a nested function is not determined by it’s parent’sthis
. It’s assigned depending on how that function is invoked. - Hence invoking function without a context always makes
this
refer to the global object by default.
Key:
this
is not assigned a value until anobject
invokes the function.
Pitfalls
Up with basics, let’s start with some cases to grab the points more clearly.
Case 1: this in the global scope.
this
this.a = 1
a
Output
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
1
As mentioned if no function call is made, then execution stack is just left with global context and hence this
always refers to the global object.
Case 2: this inside a function, invoked without a context.
function b(){
console.log(this);
function c(){
console.log(this);
}
c();
}
b();
this.b();
Output
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Here function b(global) & function c(local to b) have been invoked without a context. When a function is called without an object, be it global or local, it’s this
always refers to the global context and hence the global object of the environment.
It’s not because of c() accessing the value of b()’s this, that they have the same value, but rather because of invoking without an object.
Case 3: this inside a method of an object
var d ={
e: 1,
f: function(){
console.log(this.e);
}
}
d.f();
Output
1
Here function f() is called with an object d which would be passed to the newly created context and hence this here refers to the method’s object.
Above three cases might seem obvious and not much of interest, so let’s look at the tricky ones.
Case 4: this within closures. or simply put, this inside a function defined within an object’s method, invoked without a context. Stay calm and see the example :)
var g ={
h: 1,
i: function(){
console.log(this);
function j(){
console.log(this);
}
j();
}
}
g.i();
Output
{h: 1, i: ƒ}
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Here function i() has been invoked by an object ‘g’ and hence it’s this
refers to the object which invoked the function. Case 3 already confirms it’s validity.
However what about function j(), which is defined and invoked inside i() without an object. Well, going by the classical understanding of languages we might assume that because j() lies in the context of i(), it would surely have access to this
of i() and would have the same value in and out. But here is the catch, this
is a special property and is not available as is. It’s value is made available at the time of function execution and is derived from the object that invoked the function.
At the time of execution function j() (local to i()) was invoked without specifying an object context and hence as mentioned in case 3, it’s this
refers to the global object and hence the output.
Case 5: this inside a callback. i.e. callback with some object’s method
var user = {
data: [
{ name: "Ananya", age: 23 },
{ name: "Viraj", age: 27 }
],
clickHandler: function (event) {
console.log(this.data[0].name + " " + this.data[0].age);
}
}
user.clickHandler();
$ ("button").click (user.clickHandler);
Output
Ananya 23
Cannot read property '0' of undefined
The button is wrapped inside a jQuery $ wrapper, so it is now a jQuery object.
Here we expect to log user’s data property on click of the button. ClickHandler, being a method of user object needs to be prefixed by ‘user.’ to identify and access it.
The first invocation of the method is via object user, so during it’s execution, this refers to the user object. However, the second invocation is via the button object, and so it’s reasonable to assume that the button object is passed to the execution context and hence this
refers to the button object.
The clickHandler is prefixed with the user just in order to identify and get access to the function but invocation it’self is being driven by the invoking object. Now, since there is no data property on the button object, the output will be undefined.
Case 6: this within method assigned to a variable i.e. callback with some object’s method
//global variable
var data = [
{ name: "Sumit", age: 12 },
];
var user = {
data: [{ name: "Ananya", age: 23 },],
showData: function (event) {
console.log(this.data[0].name + " " + this.data[0].age);
}
}
var showUserData = user.showData;
showUserData();
Output
Sumit 12
The this
variable is bound to another object if we assign a method that uses this to a variable.
On executing the showUserData function, the values printed to the console are from the global data array, and not from the data array in the user object.
This is again because the user’s showData is being assigned to the global variable showData, however invocation is being done without an object. hence the output.
Case 7: this when borrowing methods.
var gameController = {
scores: [20, 34, 55, 46, 77],
avgScore: null,
players: [
{ name: "Tommy", playerID: 987, age: 23 },
{ name: "Pau", playerID: 87, age: 33 }
]
}
var appController = {
scores: [900, 845, 809, 950],
avgScore: null,
avg: function () {
var sumOfScores = this.scores.reduce(function (prev, cur, index, array) {
return prev + cur;
});
this.avgScore = sumOfScores / this.scores.length;
}
}
gameController.avgScore = appController.avg();
console.log(gameController.avgScore);
console.log(appController.avgScore);
Output
null
876
Here we might expect the gameController object to use the appController avg method to compute the avgscore of the gameController object. However when executing the function, it is being invoked by the appController it’self and hence is passed to the object as this.
Conclusion
So this sums up the typical pitfalls of this
. Even though the concept is plain and simple, but chances are quite likely of making such mistakes. then what’s the way out?
- One of the most common practices among developers and well adapted within frameworks is to store a reference to
this
at the start of the function/method and use it whenever a reference to this is required. It simply eliminates the independent nature of this inside a function and hence can be used like regular OO languages.
var g ={
h: 1,
i: function(){
console.log(this);
var self = this;
function j(){
console.log(self);
}
j();
}
}
g.i();
Output
1
1
- As of ES5, if we have
strict mode
enabled, JavaScript will do the right thing and instead of defaulting to the window object it will just keepthis
as undefined. - The final way of course is to use functions like call(), apply(), bind() which allow us to exclusively pass the
this
object as parameter which I will be discussing in my next article.