Chronicles of ‘this’ in JavaScript

Monika Sahai
The Startup
Published in
4 min readNov 25, 2020

Understanding how this is calculated in JS is one of the fundamental concepts in web development. Let us focus on some important points about it.

  • this is calculated at runtime
  • Unlike other languages, where methods defined in an object is always this referencing the object, in JavaScript this is free. Its value is evaluated at call time and doesn’t depend on where the caller method was declared rather on what is the object ‘before the dot’.

Now we will look at the cases where this can take different values for standard functions.

Standard Functions

Standard functions are the non-arrow functions. For e.g

var standardFunc = function() {
console.log('I am a non-arrow function');
}
function secondFunc() {
console.log('I am also a non-arrow function');
}
const arrowFunc = () => console.log('I am an arrow function');

this inside a standard function always depends on the caller object.

var value = "window-value";
var showValue = function (scope) {//--line 1
console.log("this inside " + scope + " is : ", this);
console.log(this.value);
};
var user = {
value: "object-value",
showValue: showValue, //--line 2
nestedFunc() {
console.log("Reference of container obj:", this);

function testFunc() {
console.log("This inside testFunc is:", this);
}
testFunc();
}
};
showValue("window");
user.showValue("object");
user.nestedFunc();

Now, look at the first call i.e. showValue('window'), what does this print?

  • showValue has no caller before the dot, so in this case, this will be a global window object. So it will print ‘this inside window is`, window object

Note: this is global window object by default only in case of non-strict mode in JS. In strict mode, it is undefined. We are considering non-strict mode throughout.

Next call is user.showValue('object')

  • showValue has a caller now. Caller is user object, so this inside will be user object, and hence it prints the console log as ‘this inside object is’ user object (when you run the code, it will show the object value)

What happens in third call? user.nestedFunc()

  • nestedFunc’s caller is user, so this inside it will be user.
  • What about testFunc() call inside it? testFunc has no caller before the dot. So within testFunc this will resort to Window object.
function makeUser() {
return {
name: "John",
ref: this //--this is inside a function so this will depend on the caller of the makeUser
};
};
let user = makeUser();
console.log(user.ref); // prints window, since this in a 'function' belongs to caller, caller for makeUser is global object here.

Now look at Example 2. What does console.log(user.ref) print? It prints window object. Let us see why:

  • user = makeUser();
  • this inside function makeUser depends on the caller. Who is the caller? Nobody! So it resorts to Window object.
  • user.ref prints Window object.

Cool. All good till now. What if we do this?

let obj1 = {
func: makeUser,
thisObj: this //--no function wrapper, this refers to window
}
console.log(obj1.func().ref);
console.log(obj1.thisObj);

Now, obj1.func() means we are calling makeUser but via obj1 so it has a caller this time which is user. Hence, this console prints obj1.

What about second console: obj1.thisObj ? Remember we talked about this only in a standard function till now. Here this is not enclosed in a function but is being accessed directly inside the object, so it resorts to window object.

Important: this depends on the caller (object before the dot) only when it is wrapped in a standard function otherwise it has the default value (window or undefined depending on the mode).

Another variation:

function makeUser() {
return {
name: "John",
ref() {
return this;
}
};
};
let user = makeUser();console.log(user.ref().name ); // John
  • makeUser is called , and it returns an object. Returned object has a this which is inside a function ref, so depends on caller of ref.
  • user collects that object, so it becomes
user = {
name: "John",
ref() {
return this;
}
}
  • Then calls it as user.ref().name. What does this print? John!
  • Because ref’s caller is user, so it returns user as the return value and hence prints John because user.name is John.

Phew! Are you still with me? I hope so. Because we are just getting started.
:sweat_smile:

Examples are the way to understand a concept properly. Let us see one more.

Example-4:

let worker = {
someMethod() {
return 1;
},
slow(x) { // (*)
alert("This in slow:", this);
return x * this.someMethod();
}
};

function cachingDecorator(func) {
console.log("Inside cachingDecorator:", this);
return function(x) {//--line 1
let result = func(x);
return result;
};
}
alert( worker.slow(1) ); // --line-1a the original method works
worker.slow = cachingDecorator(worker.slow); //line-2
worker.slow(5);//--line-3

Don’t worry about the logic of the method here. Let us focus on finding the correct value for this. What does last line print? It prints

  • worker.slow is the method definition which is passed to the cachingDecorator(pass by value/copy)
  • There is no caller of cachingDecorator so this inside is thewindowobject.
  • It returns a new function, which can be called with an argument (see line-1)
  • This new function definition accepts a param and calls the argument passed to cachingDecorator method which is worker.slow as in line-2
  • so worker.slow is now is equivalent to:
worker.slow = function(x){
//--remember func is original worker.slow function body.
let result = func(x);
return result;
}
  • So now when we do worker.slow(5) at line 5, x is 5 and func is worker.slow's original definition in worker object at (*)
  • so when we are calling func(x), there is no caller for this func, so this will be window, hence this.someMethod will be invalid and will throw an error as there is no method with this name in the window object.

Note: Original call at line-1a works because worker.slow is not yet modified by line-2, so caller is worker and correct method is called.

Hence, always check the caller of the method to find the correct value for this being referenced in that method. Pretty straight forward :)

What happens to this when it is inside an arrow function? Don’t stress, it is even simpler! Check it out here!

--

--

Monika Sahai
The Startup

I am a professional front end web developer, aiming to become a full stack developer. I do digital drawing and love learning new frameworks.