Function & this in JavaScript

Functions are the building blocks of JavaScript, it is one of the programming languages that uses functional programming at the core.

As easy as it is to use the functions, understanding the this keyword is that complex. Because the value of the this is decided at the execution time, unlike other programming languages.

Majorly there are four different ways to invoke a function in Javascript.
1. As a normal function.

function example(){
  console.log("Hello World!");
};

example();
// "Hello World!"

2. As a method.

const obj = {
  blog: "learnersbucket",
  displayBlog: function (){
   console.log(this.blog);
  }
};

obj.displayBlog();
// "learnersbucket"

3. As a constructor.

const number = new Number("10");
console.log(number);
// 10

4. Indirectly using call, apply, & bind.

function example(arg1, arg2){
  console.log(arg1 + arg2);
};

example.call(undefined, 10, 20);
// 30

The value of this is decided upon how the function is invoked, each invocation creates its own context and the context decides the value of this. Also the “strict mode” affects the behavior of this too.

Value of “this” when invoked as a normal function

The value of this in the function invocation refers to the global object. window in the browser and global in nodejs.

function example(){
  // in browser this refers to window
  console.log(this === window);
}

example();
// true

Because this refers to the window object, if we assign any property to it we can access it outside.

function example(){
  // in strict mode this refers to undefined
  this.blog = "learnersbucket";
  this.displayBlog = function(){
    console.log(`Awesome ${this.blog}`)
  }
}

example();
console.log(this.blog);
// "learnersbucket"

this.displayBlog();
// "Awesome learnersbucket"

Strict mode

If you invoke the function with the strict mode the value of this will be undefined.

function example(){
  "use strict"
  // in strict mode this refers to undefined
  console.log(this === undefined);
}

example();
// true

It also affects all the inner functions that are defined in the function which is declared in strict mode.

function example(){
  "use strict"
  // in strict mode this refers to undefined
  console.log(this === undefined);
   
  // inner function
  function inner(){
    // in strict mode this refers to undefined
    console.log(this === undefined);
  }
  
  // invoke the inner function
  inner();
}

example();
// true
// true

IIFE (Immediately Invoked Function Expression)

When we immediately invoke the function, it is invoked as a normal function thus depending upon the mode, the value of this inside it is decided.

// normal mode
(function example(){
  // in strict mode this refers to undefined
  console.log(this === window);
})();
// true

// strict mode
(function example(){
  "use strict"
  // in strict mode this refers to undefined
  console.log(this === undefined);
})();
// true

Value of “this” when invoked as a method

When a function is declared inside an object the value of this inside that function will refer to the object it is declared in.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    // this refers to the current object
    console.log(this === example);
    console.log(this.blog);
  }
};

example.displayBlog();
// true
//"learnersbucket"

The context is set at the time of invocation, thus if we update the value of the object property value, it will be reflected.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    // this refers to the current object
    console.log(this === example);
    console.log(this.blog);
  }
};

example.blog = "MDN";
example.displayBlog();
// true
// "MDN"

If the object is passed as a reference, then the context is shared between both the variables, the original and the one that has the reference.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    // this refers to the current object
    console.log(this === example);
    console.log(this === example2);
    console.log(this.blog);
  }
};

const example2 = example;
example2.blog = "MDN";

example.displayBlog();
// true
// true
// "MDN"

example2.displayBlog();
// true
// true
// "MDN"

But, if we extract the method and save it in a variable, and then invoke the variable, the outcome will change.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    console.log(this === example);
    console.log(this.blog);
  }
};

const display = example.displayBlog;
display();
// false
// undefined

This is because when extracted to a variable and invoked it will be treated as a normal function.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    // this refers to the window object
    console.log(this === window);
    console.log(this.blog);
  }
};

const display = example.displayBlog;
display();
// true
// undefined

The same happens when you pass the methods to the timers i.e setTimeout and setInterval. Timers invoke the function as a normal function or throw errors in strict mode.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    // this refers to the window object
    console.log(this === window);
    console.log(this.blog);
  }
};

setTimeout(example.displayBlog, 200);
// true
// undefined

If there are any inner functions inside the methods, the value of this inside them depends upon how inner function is invoked.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    function inner(){
      // this refers to the window object
      console.log(this === window);
      console.log(this.blog);
    };
    
    inner();
  }
};

example.displayBlog();
// true
// undefined

Because the inner function is invoked as a normal function the value of this is a window object.

To access the value of the parent we can use either the Fat arrow function or indirect invocation technique using call & apply

Fat arrow function

The fat arrow function does not have the this of its own, it access the this in its nearest scope.

In the below example, the fat arrow’s this refers to the this of displayBlog() which refers to the this of the object it is part of.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    const inner = () => {
      // this refers to the example object
      console.log(this === example);
      console.log(this.blog);
    };
    
    inner();
  }
};

example.displayBlog();
// true
// "learnersbucket"

Using call() method

We can change the value of this inside a function by calling it indirectly with the call method.

const example = {
  blog: 'learnersbucket',
  displayBlog: function(){
    function inner(){
      // this refers to the example object
      console.log(this === example);
      console.log(this.blog);
    };
    
    inner.call(this);
  }
};

example.displayBlog();
// true
// "learnersbucket"

Value of “this” when invoked as a constructor

The value of this in the function invoked as a constructor refers to a new object which has the value passed as an argument. Each instance creates a new object.

function Example(blog){
  this.blog = blog;
  this.displayBlog = function(){
    console.log(this.blog);
  };
};

const example = new Example("learnersbucket");
example.displayBlog();
//"learnersbucket"

const example2 = new Example("MDN");
example2.displayBlog();
//"MDN"

console.log(example === example2);
//false

Note – There are some methods in JavaScript that when invoked normally behave as a constructor.

const reg1 = RegExp('\\w+');
const reg2 = RegExp('\\w+');

console.log(reg1 === reg2);
// false

To avoid this we can add a check to the function which we want to be invoked as a constructor only.

function Example(blog) {
  if (!(this instanceof Example)) {
    throw Error('Can be invoked only as a constructor');
  }
  this.blog = blog;
};

const example = new Example("learnersbucket");
console.log(example.blog);

const example2 = Example("MDN");
// Error: Can be invoked only as a constructor 

Value of “this” when invoked indirectly

When the function is invoked indirectly the value of this is what is passed as an argument to the call, apply, & bind method.

Run time binding

Using the call and apply methods we can invoke the function with the new context. The values will be attached to that execution only.

const exampleObj = {
  blog: 'learnersbucket',
};

function example(name) {
   console.log(`${name} runs ${this.blog}`);
};

example.call(exampleObj, 'Prashant');
// "Prashant runs learnersbucket"

example.apply(exampleObj, ['Prashant']);
// "Prashant runs learnersbucket"

The difference between call and apply is that apply accepts arguments in an array, while call accepts it normally.

Permanent binding

When using bind, we can create a new function with the new values and store it in a variable, and then use it further. It creates fresh permanent binding without affecting the original function.

const exampleObj = {
  name: 'prashant',
};

function example(blog) {
   console.log(`${this.name} runs ${blog}`);
};

const bounded = example.bind(exampleObj);

bounded('learnersbucket');
// "Prashant runs learnersbucket"

bounded('MDN');
// "Prashant runs MDN"