JavaScript keyword this

The behavior of the this keyword depends on the context in which it is used and whether it is used in strict or non-strict mode.

Global context and globalThis object

In the global context, this refers to the global object. What is a “global object” in JavaScript? It depends on the environment where the code is running. So, in a web browser this, represents the window object – an object that represents the browser window. In a Node.js environment this, it represents a global object. And for web workers, it represents the self this object

For example, in a web browser when you run the following code:

console log(this);

we will get console output like the following

Window {window: Window, self: Window, document: document, name: “”, location: Location, …}

The definition of the global This object has been added to the ES2020 standard, which allows you to refer to the global context regardless of the environment and situation in which the code is executed:

console.log(globalThis);

Function context

Within a function, this refers to the outer context. For functions defined in the global context, this is the global this object. For example:

function foo(){
    var bar = "bar2";
    console.log(this.bar);
}

var bar = "bar1";

foo(); // bar1

If we didn’t use this, then the call would go to a local variable defined inside the function.

function foo(){
    var bar = "bar2";
    console.log(bar);
}

var bar = "bar1";

foo();  // bar2

But if we were to use strict mode, then this would be undefined:

"use strict";
function foo(){
    var bar = "bar2";
    console.log(this.bar);
}

var bar = "bar1";

foo();  // ошибка - this - undefined

Object context

In the context of an object, including its methods, the keyword this refers to the same object:

var o = {
    bar: "bar3",
    foo: function(){
        console.log(this.bar);
    }
}
var bar = "bar1";
o.foo();    // bar3

Examples

Let’s look at a more complex example:

function foo(){
    var bar = "bar2";
    console.log(this.bar);
}

var o3 = {bar:"bar3", foo: foo};
var o4 = {bar:"bar4", foo: foo};

var bar = "bar1";

foo();  // bar1
o3.foo();   // bar3
o4.foo();   // bar4

The global variable bar is defined here. And also the local variable bar is defined in the foo function. The value of which variable will be displayed in the foo function? The foo function outputs the value of the global variable, because this script is run in non-strict mode, which means that the this keyword in the foo function refers to an external context.

Otherwise, the situation is with objects. They define their own context in which their bar property exists. And when the foo method is called, the external context of the function will be the context of the objects o3 and o4.

This behavior can lead to some misunderstanding in individual cases. So, let’s consider another situation:

var o1 = {
    bar: "bar1",
    foo: function(){
        console.log(this.bar);
    }
}
var o2 = {bar: "bar2", foo: o1.foo};

var bar = "bar3";
var foo = o1.foo;

o1.foo();   // bar1
o2.foo();   // bar2
foo();      // bar3

Even though the o2 object uses the foo method from the o1 object, the function o1.foowill still look up the value for this.bar in the outer context, that is, in the context of the o2 object. And in the o2 object, this value is bar: “bar2”.

The same is true for the global variable foo, which refers to the same function as the o1.foo method. In this case, the value for will also be searched for this.barin the external context, that is, in the global context where the variable is defined var bar = “bar3”.

However, if we call a function from another function, the called function will also use the outer context:

var bar = "bar2";

function daz(){
    var bar = "bar5";
    function maz(){
        
        console.log(this.bar);
    }
    maz();
}
daz();  // bar2

Here, the daz function uses as its this.barvalue the value of the variable bar from the outer context, that is, the value of the global variable bar. The maz function also this.baruses the value of the variable bar from the outer context as its value, and this value this.barfrom the outer daz function, which in turn represents the value of the global variable bar. So the console will end up with “bar2” instead of “bar5”.

Explicit binding

Using the and methods call(), apply() you can explicitly bind a function to a specific context:

function foo(){
    console.log(this.bar);
}

var o3 = {bar: "bar3"}
var bar = "bar1";
foo();  // bar1
foo.apply(o3);  // bar3
// foo.call(o3);

In the second case, the foo function is bound to the o3 object, which defines its context. Therefore, in the second case, the console will display “bar3”.

bind method

The method f.bind(o)allows you to create a new function with the same body and scope as the function f, but bound to the object o:

function foo(){
    console.log(this.bar);
}

var o3 = {bar: "bar3"}
var bar = "bar1";
foo();  // bar1
var func = foo.bind(o3);
func(); // bar3

this and arrow functions

In arrow functions, the object passed via this is taken from the parent context in which the arrow function is defined. Consider the following example:

const person = {
    name: "Tom",
    say:()=> console.log(`My name is ${this.name}`)
}
person.say();   // My name is 

Here the arrow function say()is accessing a property this.name, but what does it represent here this? For an outer context where an arrow function is defined – that is, for an object context, person thisrepresents the global object (browser window object). However, the global variable nameis not defined, so the following will be printed to the console:

My name is

Now let’s change the example a bit:

const person = {
    name: "Tom",
    hello(){
        console.log("Hi");
        let say = ()=> console.log(`My name is ${this.name}`);
        say();
    }
}
person.hello();

The arrow function is now defined in the hello(). this method represents the current person object where this method is defined. Therefore, in the arrow function, this will represent the person object, and this. name- the name property of this object. So when we run the program, we get:

Hi
My name is Tom

While arrow functions can add to the annoyance of this, they can also solve a number of problems. So, when working with multiple contexts, we have to consider in which context the variable is defined. For example, take the following code:

const school ={
    title: "Oxford",
    courses: ["JavaScript", "TypeScript", "Java", "Go"],
    printCourses: function(){
        this.courses.forEach(function(course){
            console.log(this.title, course);
        })
    }
}
school.printCourses();

The printCourses function iterates through all the courses in the array and prefixes them with the value of the title property when they are printed. However, on the console when we run the program, we will see the following:

undefined “javascript”
undefined “TypeScript”
undefined “Java”
undefined “Go”

We see that the value this.titleis undefined because this as the object context is replaced by the global context. In this case, we need to pass a similar value to this.title or the entire context of the object.

const school ={
    title: "Oxford",
    courses: ["JavaScript", "TypeScript", "Java", "Go"],
    printCourses: function(){
        const that = this;
        this.courses.forEach(function(course){
            console.log(that.title, course);
        })
    }
}
school.printCourses();
[js]
Arrow functions also solve this problem:
[js]
 
const school ={
    title: "Oxford",
    courses: ["JavaScript", "TypeScript", "Java", "Go"],
    printCourses: function(){
        this.courses.forEach((course)=>console.log(this.title, course))
    }
}

school.printCourses();

The context for the arrow function in this case will be the context of the school object. Accordingly, it is not enough for us to define additional variables for passing data to the function.