DEV Community

Cover image for 'this' keyword in JavaScript
angDecoder
angDecoder

Posted on • Updated on

'this' keyword in JavaScript

'this' in JavaScript can be very confusing, especially if you already know any Object Oriented Language (like C++ or Java) because 'this' in JavaScript behaves very different.
Let's dive right into it.

Table of Contents

1. What is 'this'?
2. What 'this' is not
3. Rules for 'this' - this all makes sense

4. Rule Precedence
5. Wait there is more - Lexical 'this'


1. What is 'this'?

'this' in JavaScript is a special keyword which refers to a object and gets automatically generated in the scope of the function.

It is a runtime binding and it depends on the call site( where the function was invoked from ).

var obj = {
    name : "Superman",
    printName : function(){
        console.log(`My name is ${this.name}.`)
    }
};

obj.printName(); // My name is Superman
Enter fullscreen mode Exit fullscreen mode

Simple, isn't it? Well, not so fast. Check out this code.

var obj1 = {
    name : "Superman",
    printName : function(){
        console.log(`My name is ${this.name}.`)
    }
};

var obj2 = {
    name : 'Batman'
}

obj1.printName.call(obj2); // My name is Batman.
Enter fullscreen mode Exit fullscreen mode

How is obj2 able to access a property of obj1 and why is this pointing to obj2 ? This post will clear all doubts one by one.


2. What 'this' is not

Before understanding what this actually means, we need to understand what this is not.

i. this does not refer to itself

// program to check how many times function was called
function fun(){
    this.count++;
}

/* We can add property to functions as they are also objects in js*/
fun.count = 0; 

for( var i=0; i<4; i++ )
    fun();

console.log(fun.count) // 0
Enter fullscreen mode Exit fullscreen mode

Here, this keyword inside fun() doesn't not refer to itself, as fun.count is still 0.
Hint : this is pointing to global object. More on this later.

ii. this doesn't refer to function scope.

// this.a inside inner function doesn't
// refer to variable a of outer function
function outer(){
    var a = 4;
    function inner(){
        console.log(this.a);
    }

    return inner;
};

var f = fun1();
f(); // undefined
Enter fullscreen mode Exit fullscreen mode

Hint : Again, this is pointing to global object
Does it mean that this always points at global object ?
The answer is NO, it was just a coincidence.


3. Rules for 'this' - this all makes sense

Now, we are about to dive deep into rules which define how this is binded to objects. There are 4 basic rules :-

Types of binding in JS

3.1 DEFAULT BINDING

By default this is binded to global object. If none of the other rules apply.

var a = 'global';

function fun(){
    var a = 'local';
    console.log(this.a);
};

fun(); // global
Enter fullscreen mode Exit fullscreen mode

In strict-mode global object is not available for default binding. So, this.a would result in TypeError.

var a = 'global';

function fun(){
    "use strict"
    var a = 'local';
    console.log(this.a);
};

fun(); 
// uncaught TypeError: Cannot read properties of undefined (reading 'a')
Enter fullscreen mode Exit fullscreen mode

3.2 IMPLICIT BINDING

In implicit binding 'this' is binded to context object. Context Object is the object which contains the reference for the function called. It is also sometimes called owning or containing object.

Note :- Although the name suggests that the function is owned by the owning object(context object) it only contains the reference of the function. A function is never owned by any object.

var name = 'global';

function fun(){
    console.log(this.name);
}

var myObj = {
    name : 'myObj',
    fun : fun
}

fun(); // global -> due to default binding
myObj.fun() // myObj -> due to implicit binding

// here myObj is context object for function fun.
Enter fullscreen mode Exit fullscreen mode

Some pitfalls in implicit binding.

a. Passing a reference

var name = 'global';

function fun(){
    console.log(this.name);
}

var myObj = {
    name : 'myObj',
    fun
}

var f = myObj.fun; // only reference of fun is passed
f(); // global 
// actual call to fun is made without context object

// Remember object do not own a function
Enter fullscreen mode Exit fullscreen mode

b. Only last/top level object property reference matters

var name = 'global';

function fun(){
    console.log(this.name)
}

var obj1 = {
    name : 'obj1',
    fun
}

var obj2 = {
    name : 'obj2',
    obj1,
    fun
}

obj2.obj1.fun() // obj1
Enter fullscreen mode Exit fullscreen mode

3.3 EXPLICIT BINDING

In this type of binding, we explicitly define which object this should bind to. This can be done with the help of .bind(), .call() or .apply().

  • .call() -> It is used to change the value of this inside a function and execute it with the arguments provided.
  • .apply() -> It works just like .call() but takes arguments in a array.
  • .bind() -> It takes object as first argument, rest of the arguments are used as default argument for the function.

Below code shows how these functions work.

function introduce(city){
    console.log(`Hi, my name is ${this.name}. I live in ${city}. `)
}

var hero = {
    name : 'Spiderman'
}

introduce.call(hero,'New York');
introduce.apply(hero,['New York']);
// .call() and .apply() are immediately executed

var intro = introduce.bind(hero,'New York');
intro();
// new function (intro) is created with 'this' binded 
// to hero object. This function can be later executed.
Enter fullscreen mode Exit fullscreen mode

Hard Binding - Variation of Explicit binding
Binding using .bind() is called hard binding.Code to implement our own .bind() function .

function myBind(f,obj){
    return function(...args){
        f.apply(obj,args);
    }
};
// f -> function to be binded
// obj -> object 'this' should refer to
Enter fullscreen mode Exit fullscreen mode

3.4 new BINDING

This type of binding is done with the help of new operator. Before we move forward let us make some things clear :-

  • There are no class in JS, only objects. The class introduced in ES6 gives us false belief but under the hood they are all just functions and objects.
  • There are no contructors in JS, they are not attached to any class.
  • When functions are called with new operator in-front of them, then new object is created. It is called constructor call. Functions themselves are not constructors.

When functions are called with new operator then following things happen :

a. A new object is created out of thin air.
b. Newly created object is [[Prototype]]-linked to function.

More on [[Prototype]] will be discussed in some other blog.

c. Newly created object is set as 'this' for the function call.
d. Function automatically returns a newly constructed object ( unless the function itself returns a object itself ).

function fun(name,age){
    this.name = name;
    this.age = age;
    this.introduce = function(){
        console.log(`My name is ${this.name}. I am ${this.age} years old.`);
    }
};

var obj = new fun('Peter Parker',22);
obj.introduce();
Enter fullscreen mode Exit fullscreen mode

4. Rule Precedence

i. Explicit Binding ( my own implementation myBind() )
ii. new Binding
iii. Explicit Binding ( internal implementation )
iv. Implicit Binding
v. Default Binding

Note :- If null or undefined is passed as a 'this' binding parameter to call, bind or apply then default binding applies.


5. Wait there is more - Lexical 'this'

With the ES6, arrow functions were introduced which behaves differently than normal functions for this binding.

The arrow functions adopt the this binding from enclosing scope.

function outer(){
    var inner = ()=>{
        console.log(this.name);
    }

    return inner;
}

var myObj = {
    name : 'myObj'
}

var inner = outer.apply(myObj);
inner(); // myObj
Enter fullscreen mode Exit fullscreen mode

Arrow functions are useful when using callbacks to keep track of 'this'.

var name = 'global';

var obj = {
    name : 'obj'
}

function exec(){
    setTimeout(() => {
        console.log(this.name)
    }, 1000);
};

exec.call(obj); // obj -> after 1000ms
/* 
Normal function as callback would 
print global after 1000ms as 'this' would
refer to global object
*/
Enter fullscreen mode Exit fullscreen mode

In pre ES6 era, closure were used to keep track of this.

var name = 'global';

var obj = {
    name : 'obj'
}

function exec(){
    var self = this;
    setTimeout(function(){
        console.log(self.name)
    }, 1000);
};

exec.call(obj); 
Enter fullscreen mode Exit fullscreen mode

That is it for this blog. If you learned something from this blog then please like and share. If I made a mistake in this blog then please comment I would surely get back to it.
I am planning to write more blogs like this, so stay tuned.
Also, connect with me on linkedin

Top comments (1)

Collapse
 
manish_kumar3_1 profile image
Manish

Thanks man