Understanding: Context, Scope, Execution Context and 8 different This value in JavaScript explained by Paw Patrol!
Introduction
After more than 6 years explaining frontend to both vocational training and university students, I have found it difficult to understand the use of the reserved word "this" in JavaScript. The problematic of this reserved word has been hidden with the appearance of TypeScript and frameworks such as Angular, although under these layers there is still JavaScript, and the lack of knowledge causes errors that takes hours to fix.
Definitions
Context Vs Scope
The first concepts we have to clarify is the difference between context and scope. These two terms are confused by many frontend developers (I myself took a while to understand them).
All the functions have associated scope and context. Scope defines the access to variables of a function when the function is invoked. On the other hand, Context is always the value of the reserved word this
which is a reference to the object that owns the execution of the code.
Execution Context
JavaScript is a single-threaded language, so it can just execute one task at the same time. The rest of tasks are queued in the Execution Context. Unfortunately, when they say 'execution context', they mean scope (why did they do that?).
In each call, a function appends its context to the execution context. So each function creates its own execution context (its own scope)
Once the call ends, the context gets destroyed and the execution context will be transfered to the parent context. There is only one global context but finite function contexts.
Understanding this
“this” refers to global object
By default the execution context for an execution is global, which means that if a code is being executed as part of a simple function call then “this” refers to global object. In the case that you run your code in a browser the global object is "window" object while that in node.js the global object can be the special "global" or the "module.exports".
The following code is running in a browser.
console.log('browser - global - this');
console.log(this)
console.log('browser - global this is window at the module level');
console.log(this === window); // true
function anyFunction()
console.log('browser - global this is window object inside any function');
console.log(this); // this is a global "window" object
}
anyFunction();
(function () {
console.log('browser - global this is a window object inside any function (included IIFE)');
console.log(this); // this is a global "window" object
})();
The following code is running in a node.js environment.
console.log('node.js - global - this');
console.log(this)
//So, this, at the module level, is actually the exports object.
console.log('node.js - global this is module.exports at the module level');
console.log(this === module.exports);
function anyFunction() {
// this is a global nodeJS object
console.log('node.js - global this is a node.js object inside any function');
console.log(this === global);
}
anyFunction();
(function () {
console.log('node.js - global this is a node.js object inside any function (included IIFE');
console.log(this === global)
})();
"this" refers to new instance
When a function is invoked with “new” keyword then the function is known as constructor function and returns a new instance. In such cases, the value of “this” refers to newly created instance.
The new keyword performs following four tasks:
- It creates new empty object e.g. obj = { };
- It sets new empty object's invisible 'prototype' property to be the constructor function's visible and accessible 'prototype' property. (Every function has visible 'prototype' property whereas every object includes invisible 'prototype' property)
- It binds properties or functions which are declared with
this
keyword to the new object. - It returns a created object unless the constructor function returns a non-primitive value (custom JavaScript object). If constructor function does not include return statement then compiler will insert 'return this;' implicitly at the end of the function. If the constructor function returns a primitive value then
return this;
will not be inserted.
function Dog() {
console.log('this is not a global scope')
console.log(this === module.exports); //this context is not windows
}
new Dog();
Now that we have a new execution context new properties can be defined for Dog function, in this case we got 2 new dogs of Paw Patrol, Turbot and Rubble.
function Dog(name, job) {
this.name = name;
this.job = job;
this.displayName = function () {
console.log(`this is in the Dog context:: ${this.name} ${this.job}`);
}
}
const turbot = new Dog('Turbot', 'Police');
const rubble = new Dog('Rubble', 'Worker');
turbot.displayName();
rubble.displayName();
As you may know, the great advantage of Javascript is using the prototype of each function (I recommend you to read about prototype pattern which is used in other languages object oriented, due to they are not native).
function Dog(name, job) {
this.name = name;
this.job = job;
}
Dog.prototype.displayName = function () {
console.log(`this is in the Dog context:: ${this.name} ${this.job}`);
}
const turbot = new Dog('Turbot', 'Police');
const rubble = new Dog('Rubble', 'Worker');
turbot.displayName();
rubble.displayName();
“this” refers to invoker object (parent object)
In Javascript, the object's properties can be a function or a simple value. When a object's method is invoked then "this" refers to the object which contains the method which is being invoked.
In the following example you can see how the this value is different depends of the execution context.
function anyFunction() {
console.log("any function");
console.log(this === global);
}
const user = {
name: 'Carlos Caballero',
anyFunction: anyFunction,
otherFunction: function () {
console.log(this === global);
}
}
anyFunction(); // Prints true because "this" referes to global object
user.anyFunction() // Prints false because now “this” refers to user object instead of global object.
const fun1 = user.otherFunction;
fun1() // Prints true as this method is invoked as a simple function.
user.otherFunction() // Prints false on console as otherFunction is invoked as a object’s method
“this” with call or apply methods
A function in JavaScript is also a special type of object. Every function has call
, bind
and apply
methods. These methods can be used to set custom value of “this” to the execution context of function.
In the following code you can see how to change the execution context using the call method.
function Dog(name, job) {
this.name = name;
this.job = job;
}
Dog.prototype.displayName = function () {
console.log(`Dog:: ${this.name} - ${this.job}`);
}
const turbot = new Dog('Turbot', 'Police');
const rubble = new Dog('Rubble', 'Worker');
turbot.displayName(); // Prints Turbot info
rubble.displayName(); // Prints Rubble info
turbot.displayName.call(rubble); // Here we are setting value of this to be rubble object
//Prints Rubble info
“this” with bind method
The bind
method returns a new method with “this” refers to the first argument passed.
function Dog(name, job) {
this.name = name;
this.job = job;
}
Dog.prototype.displayName = function () {
console.log(`this is in the Dog context:: ${this.name} ${this.job}`);
}
const turbot = new Dog('Turbot', 'Police');
const rubble = new Dog('Rubble', 'Worker');
turbot.displayName();
rubble.displayName();
const newDisplayName = turbot.displayName.bind(rubble); // / Creates new function with value of “this” equals to rubble's object
newDisplayName();//Prints Turbo info
“this” with fat arrow function
As part of ES6, there is a new way introduced to define a function; using fat arrow
(=>).
const display = (name, job) => {
console.log(`Dog: ${name} - ${job}`);
};
When a fat arrow is used then it doesn’t create a new value for “this”. “this” keeps on referring to the same object it is referring, outside the function. There is not a new execution context.
The following code the value of this is the function growUp
which there is not a age
attribute in its execution context.
// Option bad
function Dog(name, job) {
this.name = name;
this.job = job;
this.age = 0;
this.displayName = function () {
console.log(`Name: ${this.name} ${this.job}`);
}
setInterval(function growUp() {
this.age++; // this is growUp "this" value
console.log(this.age);
}, 1000);
}
const turbot = new Dog("Turbo", "Police");
The solution, in this case is not creates a new execution context. Therefore, we can use a fat-arrow.
function Dog(name, job) {
this.name = name;
this.job = job;
this.age = 0;
this.displayName = function () {
console.log(`Name: ${this.name} ${this.job}`);
}
setInterval(() => {
this.age++; // this is Dog value
console.log(this.age);
}, 1000);
}
const turbot = new Dog("Turbo", "Police");
"this" with fat arrow function and apply-call
When you use the apply or call methods over a fat-arrow function the this is not changed due to the fat-arrow does not own this
. Therefore, the apply and call methods only call the original method with parameters while "thisArg" is ignored.
function Dog(name, job) {
this.name = name;
this.job = job;
this.age = 0;
this.add = function (a) {
const f = v => v + this.age;
return f(a);
};
this.addThruCall = function (a) {
const f = v => v + this.age;
const b = {
age: 2
};
return f.call(b, a);
}
this.displayName = function () {
console.log(`Name: ${this.name} ${this.job}`);
}
}
const turbot = new Dog("Turbo", "Police");
console.log(turbot.add(1)); // Prints 1
console.log(turbot.addThruCall(1)); // Prints 1 although you can expect 2
"this" with class sugar syntax
When we use class
sugar syntax, it is common to use this
the same way as any other object oriented programming language. However, the mayority of OOP languages do not allow defining functions within functions.
Theferore, if we have a look at the following code, there is a method displayName
which includes a method called innerDisplay
which uses the keyword this
. If we execute innerDisplay
function in this context we will be creating a new execution context so this
value will not belong to Dog
class. Nevertheless, in order to solve this issue we can use any of the tricks explained along this blog. In this case, we will use aply
function to change the context of innerDisplay
function to Dog
's context.
class Dog {
constructor(name, job) {
this.name = name;
this.job = job;
this.age = 0;
}
displayName() {
console.log(`Name: ${this.name}`);
function innerDisplay() {
console.log(`Job: ${this.job}`);
}
innerDisplay(); // undefined
innerDisplay.apply(this); // Job: Police
}
}
const turbot = new Dog("Turbo", "Police");
turbot.displayName();
More, More and More...
https://www.tutorialsteacher.com/javascript/new-keyword-in-javascript
https://medium.com/quick-code/understanding-the-this-keyword-in-javascript-cb76d4c7c5e8
https://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/
http://ryanmorr.com/understanding-scope-and-context-in-javascript/
https://medium.com/@marjanrab/javascript-scope-context-and-this-under-the-hood-43c32033c9f9
https://scotch.io/tutorials/understanding-scope-in-javascript
https://medium.com/javascript-in-plain-english/hello-javascript-this-bb97c54f0823