Chronicles of ‘this’ in JavaScript
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 isuser
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 functionref
, so depends on caller ofref
.- 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 returnsuser
as the return value and hence prints John becauseuser.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 thecachingDecorator
(pass by value/copy)- There is no caller of
cachingDecorator
so this inside is thewindow
object. - 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 isworker.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 isworker.slow's
original definition inworker
object at(*)
- so when we are calling
func(x)
, there is no caller for this func, so this will be window, hencethis.someMethod
will be invalid and will throw an error as there is no method with this name in thewindow
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!