DEV Community

Dhilip kumar
Dhilip kumar

Posted on

Understanding `this` with E.D.I.N concepts

What is this in javascript?

this keyword refers to an object, the object that is executing the current part of our Javascript code.

Why is it so complicated to understand?

We might feel overwhelmed as there are quite a number of ways to manipulate this by providing it a different context.

this binding has nothing to do with where a function is declared but it has everything to do with how a function is invoked.

There are 4 main Rules which we can make use of, to determine what this in your code represents.

E.D.I.N - Stands for Explicit binding, Default binding, Implicit binding and new Binding.
(There is no EDIN concept, its just my way to remember these concepts)

Rule 1.Implicit Binding:

Implicit Binding is achieved when the function that is executed is called with a context.

Example:


const obj = {
    name: 'Dev',
    getName : function() {
        console.log(this.name);
    }
}

obj.getName(); // Dev
Enter fullscreen mode Exit fullscreen mode

obj.getName()

  • Here we call the getName() function of the object obj with obj as the context.
  • Since obj has name property, this.name would give out dev as the output.

What if your function is nested within objects?

Example:


const obj = {
    name: 'Dev Child',
    getName : function() {
        console.log(this.name);
    }
}

const parentObj = {
    childObj: obj,
    name: 'Dev Parent'
};

parentObj.childObj.getName(); // Dev Child

Enter fullscreen mode Exit fullscreen mode
  • The last level parent before any function call is the context for that function.
  • In our case obj is the this for getName() function.

The Fat Arrow function catch:

var name = 'Global Dev';
const obj = {
    name: 'Local Dev',
    getName : () => {
        console.log(this.name);
    }
};

obj.getName() // Global Dev 😈 😈 😈

Enter fullscreen mode Exit fullscreen mode

  • The problem here is this binding has been done for the getName arrow function and it takes Lexical this which is Gloabl in this case.
  • So calling the function with impicit binding takes lesser priority than Arrow function.

2.Default Binding:

Default binding is whenever the function is called without any context.
A common mistake occurs while destructuring a function from an object which has this context in it.


var name = 'Global Dev';

const obj = {
    name: 'Local Dev',
    getName : function() {
        console.log(this.name);
    }
};

const { getName } = obj;
getName(); // Global Dev  πŸ€”πŸ€”πŸ€”

Enter fullscreen mode Exit fullscreen mode
  • Here we have destructured the getName function out of the object obj.
  • Then, we have called it without any context
  • It means the function execution here is happening with Global context.
  • So while execution if it encounters any this in the code that will try to resolve its value by checking window object.
  • We have 'Global Dev' value assigned to name in window object so this.name resolved to window.name which is 'Global Dev'.
  • Note: The same would have resulted in returning undefined in strict mode.

How to Overcome this?
By using Explicit binding

3.Explicit Binding:

Explicit binding is a process of specifying what this object is, while calling the function. It is usually done with the help of the famous Js trio call, apply and bind.

var name = 'Global Dev';
const obj = {
    name: 'Local Dev',
    getName: function() {
        console.log(this.name);
    };
};

const { getName } = obj;
const newContext = {
  name: 'New Dev'
};
// Explicit binding takes priority over Default binding 
getName.call(newContext); // New Dev 

// Explicit binding takes priority over Implicit binding 
obj.getName.call(newContext); // New Dev 

Enter fullscreen mode Exit fullscreen mode

It is clear from the above that, Explicit bindings take priority over Implicit or Default Binding.

But does it have a Higher priority than Arrow function's this binding.
No!

var name = 'Global Dev';
const obj = {
    name: 'Local Dev',
    getName: () => {
        console.log(this.name);
    }
};

const newContext = {
  name: 'New Dev'
};

//Arrow function's bind took priority over Explicit binding.
obj.getName.call(newContext); // Global Dev 

Enter fullscreen mode Exit fullscreen mode

Priority Ordering:

Arrow Function > Explicit Binding > Implicit Binding > Default Binding

4.new Binding:

If the function is called with new operator in the prefix then the newly constructed object is the this reference here.

  function MyName() {
    this.name = "Local Dev";
  }

  MyName.prototype.getName = function() {
    console.log(this.name);
  }

  // `new` binding
  const name_instance = new MyName();
  console.log(name_instance.name);    // Local Dev
  // Implicit Binding
  name_instance.getName();            // Local Dev

Enter fullscreen mode Exit fullscreen mode
  • Here we have defined our instance variable name_instance which is formed from new opertor operating on factory function MyName.
  • All references to this in MyName function refers to the newly created object instance name_instance

All our Rules from 1 to 3 applied to this instance (name_instance):

  // Default Binding
    var name = "Global Dev"
    const {getName} = name_instance;
    getName();                                      // Global Dev

  // Implicit Binding
    name_instance.getName();                        // Local Dev

  // Explicit Binding
    name_instance.getName.call({ name: 'New Dev'}); // New Dev

  // Arrow Binding
    MyName.prototype.get_Arrow_Name = () => {
      console.log(this.name);
    }
    name_instance.get_Arrow_Name();                  // Global Dev
Enter fullscreen mode Exit fullscreen mode

React Classes:

  class App extends React.Component {
    constructor() {
      this.handle_three = this.handle_three.bind(this);
    }

    handle_one() {
      console.log(this);
    }

    handle_two = () => {
      console.log(this);
    }

    handle_three() {
      console.log(this);
    }

    render() {
      return (
        <React.Fragment>
          {/* Default binding */}
          <div onClick={this.handle_one}></div> 
          {/* Arrow Function */}
          <div onClick={this.handle_two}></div>
          {/* Expicit Binding at constructor*/}
          <div onClick={this.handle_three}></div>
        </React.Fragment>
      )
    }
  }
Enter fullscreen mode Exit fullscreen mode
  • Handlers on JSX elements will call the function declared.
  • handle_one attachment results in calling the function with no context(Default binding). This results in undefined because React ensures Default binding results in undefined rather than a Global.
  • handle_two attachment results in calling the function with the newly created Intsance's (Current App Class's instance's) context.
  • handle_three attachment results in explicit binding to provide value for this at the constructor.

Lets Celebrate now!

Hope you guys have Enjoyed this Article πŸ˜„

Reference: Kyle Simpson's this & Object Prototypes

My Website, blogs and Twitter

Thats all Folks!!!

Top comments (1)

Collapse
 
thepeoplesbourgeois profile image
Josh

Very informative post! I just wanted to mention that in the case of events handled by addEventListener, if you pass an object that implements #handleEvent, it will be called with event as its argument, and it will be implicitly bound to the object passed in to addEventListener. If you call any other functions on the object within the body of handleEvent, they will also be implicitly bound to the object, too! This saves memory by preventing you from having to use #bind or the arrow syntax for those functions, which both create copies of the function in question,. In circumstances where you have a large number of objects, this can save you a ton of overhead!