The most misconceived feature in JavaScript is seen as basic knowledge in other languages. The this keyword still is a confusing topic even after a long day reading books and tutorials on its inner workings.

Not like we don't know what it is -- we do. But we just always miss something when it's time to put the knowledge into practice.

"this" used to be this, but "this" is now that

Table of Contents

    This confusions and misconceptions is the reason for these standing four rules. Follow them religiously, and you will never fall victim of "this" madness. Here are the rules:

    1. Default (Global) binding rule.
    2. Implicit binding rule.
    3. Explicit binding rule
    4. Constructor calls with new

    My advice: write these rules on a sticky note and put them beside your desk.

    Before we start exploring, let's throw a little light on why exactly "this" is misconceived.

    The Confusion

    When you write a piece of code, your mental model is always centered around the creation time. The creation time is the period of creating/writing this piece of code. JavaScript has another period called execution time which is when it's interpreting what you have written.

    It's at execution time that bindings, references, and memory assignments are done. This is expected -- it's a mutual behavior in most programming languages.

    The issue in JavaScript is that at runtime (execution) it binds values based on context. This implies that a piece of logic is not necessarily interpreted the way it's written or read (by the user) but based on what is called it's execution context.

    Execution Context

    Let me give you an illustration of the execution context.

    You have three security robots that patrol every night in your neighborhood. Say robots A, B, and C. From 9 PM to 11 PM, robots A and B are on duty while C is charging. At 11 PM, C is fully charged and replaces A. Now we have B and C on duty. This shifting continues at an interval of 2 hours.

    Assuming that while at duty (execution), the pair needs to communicate each others location and inspected areas. At the first turn, A, and B were communicating. When A was substituted with C, A is not expected to continue communications with B; rather it should be interacting with C.

    Execution context changed, the environment is not the same any longer. A cannot be talking to B after a switch but with C. Understanding that A should not be talking to B any longer is where we get confused about the "this" keyword.

    this can be seen as the current situation our robots find themselves in.

    Now that you know why it always gets you confused let's see our four rules on avoiding the confusion.

    Rule 1: Default (Global) Binding

    First, take a close look at the following example and make a wild guess what could be logged to the console. Feel free to get it wrong:

    // 1A
    function printName() {
      console.log(this.name);
    }
    
    var name = 'Scotch';
    
    printName();

    Keep the answer in your mind, then try to guess this one:

    // 1B
    function printName() {
      'use strict';
      console.log(this.name);
    }
    
    var name = 'Scotch';
    
    printName();

    Let's analyze:

    When not in strict mode, this refers to the global scope. When in strict mode it's bindings are undefined.

    Ex 1A will print "Scotch" to the console because "this" refers to the default binding which is global.

    In Ex 1B, "this" could have also referred to the global object but because of "use strict", it defaults to undefined. use strict will make sure that the scope of this will be the function scope and not the global scope.

    https://codepen.io/codebeast/embed/preview/EXqLZP

    See the Pen EXqLZP by Chris Nwamba (@codebeast) on CodePen.

    Rule 2: Implicit Binding

    What if the function is not exposed to the global context. What do you think about the following?

    var company = {
      name: 'Google',
      printName: function printName() {
        console.log(this.name);
      }
    };
    
    var name = 'Scotch';
    
    company.printName();

    This time, this is keeping a reference to the object that encloses it's wrapping function. Therefore, the rule states:

    "this" refers to call site when enclosed in an object

    Mark the words "call site." The rule did not say "write site," but "call site." The above example will print "Google," but don't expect the following example to print the same thing:

    var company = {
      name: 'Google',
      printName: function printName() {
        console.log(this.name);
      }
    }
    
    var name = 'Scotch';
    
    company.printName();  // Google
    
    // print name call site
    // won't be the object
    // but global
    var printNameAgain = company.printName;
    
    printNameAgain(); // Scotch

    This example prints "Scotch" and not "Google" because the call site was not the object but the global object. Remember our security robots illustration; the execution context is what matters.

    See the Pen YQmLNx by Chris Nwamba (@codebeast) on CodePen.

    Rule 3: Explicit Binding

    What if we want printNameAgain to refer to the company object even after the call context is not the company object but the global object?

    The explicit binding rule:

    You can explicitly manipulate the call site using call, apply, or bind.

    We can manually make printNameAgain refer to the company object as its call site:

    var company = {
      name: 'Google',
      printName: function printName() {
        console.log(this.name);
      }
    }
    
    var name = 'Scotch';
    
    var printNameAgain = company.printName;
    
    printNameAgain.call(company);

    Usually, when we call printNameAgain it referred to the global object, but we now use the call method to hard-bind company as its execution context.

    What About Parameters?

    When the function has a parameter, you can just pass it in after the context:

    var company = {
      name: 'Google',
      printName: function printName(prefix, suffix) {
        console.log(prefix + this.name + suffix);
      }
    }
    
    var name = 'Scotch';
    
    var printNameAgain = company.printName;
    
    // hard bind company to printNameAgain
    printNameAgain.call(company, 'Hi ', '!!');

    The printName function expects a prefix and suffix parameters. When calling the passed function (printNameAgain) with call, we pass in the parameters after the company object binding.

    See the Pen pwMKmW by Chris Nwamba (@codebeast) on CodePen.

    The apply method allows you to pass in the arguments as an array:

    printNameAgain.call(company, ['Hi ', '!!']);

    The last one is bind. It acts a little differently from call and apply. call and apply executes the function and unlike them, bind doesn't. What bind does is to bind the correct context and return the function with the adjusted context:

    var printFunc = printNameAgain.bind(company);

    Then you can call the returned function when needed:

    var printFunc('Hi ', '!!');

    4 Constructor Calls with new

    The last rule surfaces whenever you see a function called with the new keyword:

    new keyword creates an imaginary object in a function which is linked to the prototype object. This imaginary object is returned implicitly by the function if there is no existing return statement in it.

    Let's break this down as simple as possible

    If I have the following function:

    function Company() {
      this.name = 'Scotch'
    }

    And then attempt to call the function with new:

    new Company();

    The comments below show ideally what happens internally:

    function Company() {
      // var this = {};
      this.name = 'Scotch'
      // return this;
    }

    An imaginary object is created called this and if the function doesn't return anything, this is returned. Therefore you can use the function call as an object:

    var companyInstance = new Company();
    
    console.log(companyInstance.name) // prints Scotch

    The rule also says this object is linked to the prototype object. Prototype linking is beyond the scope of this article, but it would cause no harm if we see an example of such links:

    function Company() {
      this.name = 'Scotch'
    }
    
    Company.prototype.getName = function() {
      return this.name;
    }
    
    var companyInstance = new Company();
    console.log(companyInstance.getName()); // Scotch

    We just attached a method getName to the Company's prototype object. This method has access to the same this object which was created in Company.

    TIP: A function that that will be called with the new keyword is called a Constructor Function and usually written in title case, while a call to the function with the new keyword is called a Constructor Call.

    Order of Precedence

    It's essential to note that the rules are applied in order of precedence. Meaning that if two or more of rules are found in the same scenario, there is an order in which they are applied. It's as follows:

    1. Constructor calls (rule 4)
    2. Explicit binding (rule 3)
    3. Implicit binding (rule 2)
    4. Default binding (rule 1)

    As you can see, just reversing the rules shows their order of precedence.

    Common Pitfalls: Async Handling

    There are a few known pitfalls with the this binding. They are most noticed in async logics that receive callbacks as handlers. These handlers are sometimes bound to a different context making this behave unexpectedly.

    Event handling is one such situation. Event handlers are callback functions that are passed to events registrations. These callback functions are executed at runtime when the event is triggered. Here is a basic example:

    button.addEventListener('click', function() {
      console.log('clicked')
    });

    The browser needs to give some contextual information about the event. It does this by binding to the this in the function. So you could get details about the event from the keyword:

    button.addEventListener('click', function() {
      console.log(this)
    });

    This is the intended behavior until you need to access this from an outer context. See:

    var company = {
      name: 'Scotch',
      getName: function() {
        console.log(this.name)
      }
    }
    
    // This event's handler will throw an error
    button.addEventListener('click', company.getName)

    The click event handler is a method in the company object. Event handlers as we already mentioned had their own this binding, but then we need access company via this. Running the above sample will throw an error telling you that name property could not be read. Worse still, the name property exists on an event's this property thereby generating weird results and not even throwing errors.

    To fix this, we need to bind context manually:

    var company = {
      name: 'Scotch',
      getName: function() {
        console.log(this.name)
      }
    }
    
    // bind getName's context as 'company'
    button.addEventListener('click', company.getName.bind(company))

    Conclusion

    Yeah, that's it. That's what this does and nothing more. Next time you meet in a code sample, don't fidget because it's holding contextual information about the running (not written) code.

    To debug this, avoid reason about it from the code editor. Pull out your browser, and add breakpoints where they are encountered. That way, you would learn exactly what a this binding is storing and where the values might be coming from.

    Chris Nwamba

    102 posts

    JavaScript Preacher. Building the web with the JS community.