Understanding "this" in Javascript

in #javascript7 years ago

When speaking of the keyword "this" in JavaScript things can get a little weird, partly because the keyword is named "this" so you end up having conversations that should like this (see did it here already!), "See this this here, that this refers to this context in this function right here...", and partly because the value of "this" (I will be using quotations to demarcate the keyword this as opposed the normal pronoun this) can change depending on where and how it is used. This tutorial aims to clear the fog and help you understand "this", and hopefully help you in future programming endeavors.

Node VS Browser Usage

Let's start with the basics and in doing so we need to differentiate between the basic usage of "this" in the browser and in node. Let's say we have the following code:

        
            var a = 5;
            console.log(this.a);
            console.log(a)
        
    

Browser Result:

        
            5
            5
        
    

Node Result:

        
            undefined
            5
        
    

So, we created a variable a that has a value of 5, and we got two different results depending on the environment. This is because Node uses a module loading system that will turn each file you create into its own module. So, what we see happening in Node is “this” refers to our current module, but the variable a we created exists outside that module. For the curious the module wrapper looks like this:

        
            (function (exports, require, module, __filename, __dirname) {
                    // Your module code actually lives in here
            });
        
    

Did you notice something strange here though? I just said that Node takes our file and wraps it in the above function to create a module. If that is the case then why would a not be a part of the module, and thus be a property of “this”, as it is for the browser? That is because, without getting too deep into a discussion about Node internals, the module only matters when it is being exported with either the exports or module.exports property, and with either of those you either export a list of values (exports) or an object (module.exports). The file in node can then be used by other when using the keyword “require”. What this means is that when the function is exported the value of “this” will correspond to what we included in exports, for example:

file1.js:

        
            let a = 5;
            let b = function() {
                    return this.a + 1
            };
            
            module.exports = {
                    a: a,
                    b: b()
            }
        
    

file2.js:

        
            let letters = require('./index');
            console.log(letters.a);
            console.log(letters.b);
        
    

Result:

        
            5
            6
        
    

For the truly masochistic (raise a glass brothers!) here are all the details.

In the browser, outside of a function the context is always what is referred to as the global context, which in the case of the browser is the window object, in the case of Node this object is called global. Any variable that is declared outside of a function at the root level will be added to the global context and thus to the window object. So what we will see in the browser from our first example is that not only will you get this.a = 5 and a = 5, but you will also get window.a = 5.

Functions

The value of “this” within a function depends on how that function is called, whether strict mode is being used or if the function is a part of an object. When a normal function is called any “this” used inside refers to the global context, except in the case where strict mode is in use.

Normal function:

        
            function normalFunc() {
                    console.log(this);
            }
            normalFunc();
        
    

Result:

        
            true
        
    

Strict Function:

        
            function strictFunc() {
                    'use strict'
                    console.log(this === global)
            }
            strictFunc();
        
    

Result:

        
            false
        
    

Object Function:

        
            myObj = {
                    objectFunc1: function() {
                            console.log(this === myObj)
                    },
                    objectFunc2: function() {
                            'use strict'
                            console.log(this === myObj)
                    }
            }
            myObj.objectFunc1();
            myObj.objectFunc2();
        
    

Result:

        
            true
            true
        
    

You can see the differences in the results by the function type: The normal function returns the global object as expect, in the function using strict mode we get an undefined, and both functions in the object return identical objects despite one using strict and one not.

Why does strict mode return undefined when the function is called by itself, but has a value when the function is a part of an object? When strict mode is set the current value of “this” will be whatever it was set to before the execution of the statement, since “this” was not explicitly defined we get an undefined result. How can we then get “this” to have a value within a function using strict mode? We’ll get to that in a bit but first let’s talk about what happened with the functions stored in the object. This one is a pretty simple rule to remember, functions in an object always have the context of that object. That is why the function using strict mode was still able to return a value, the context was already set by the object itself.

Apply, Call and Bind

Previously I stated that in functions using strict mode outside of an object the keyword “this” would return undefined because its context had not been set before being used. We can define its context, and in fact the context of any function using either the apply, call or bind function. Call and apply are almost exactly alike while bind is a little different. Let’s look at call and apply first.

You can attach a new context to any function by using the call or apply functions, the only difference between the functions is that in call the parameters for the function are additional parameters in the signature of call, and in apply the functions parameters are passed as an array.

        
            function multiply(num1, num2) {
                    'use strict'
                    console.log(this.newNum * num1 * num2);
            }
            
            multiply.call({newNum: 5}, 2, 5);
            multiply.apply({newNum: 6}, [2, 5]);
        
    

From the usage above, we cansee what these functions are useful for. In cases where you would need a function to be used in different contexts you could opt to use call or apply on a function that exists outside of those contexts instead of having to define a similar function in each.

Bind is the same as call and apply, in that it will apply a new context to a function but it’s use case is slightly different in that it is not a function call, meaning calling bind does not call the function, it returns a function that has the context assigned permanently bound to it.

Why would we want to do this? Let’s say we have two classes, Class2 uses Class1 as a member, and calls functions from Class1, this is normally ok, except when Class2 wants to assign its own property to a function that uses “this” in Class1:

class1.js:

        
            class Class1 {
                    constructor(value1) {
                            this.value1 = value1;
                    }
            
                    getValue1() {
                            return this.value1;
                    }
            }
            
            module.exports = class1;
        
    

class2.js:

        
            var Class1 = require('./class1');
                
            class Class2{
                    constructor() {
                            this.c = new Class1(1);
                            this.showValue = this.c.getValue1;
                    }   
            }
            
            c2 = new Class2();
            console.log(c2.showValue());
        
    

Result:

        
            undefined
        
    

What is happening here is that since this is an assignment in Class2 rather than a simple function call, the functions context is that of Class2 rather than context of Class1, to solve this we use bind to permanently apply the correct context to this function, since it should always be called with the context of Class1. To do this we simply add the following to Class1's constructor:

        
            this.getValue1 = this.getValue1.bind(this);
        
    

Result:

        
            1
        
    

This is a situation that more than likely arise when using events supplied by libraries and frameworks. This is a common pattern in ReactJS where you can create event listeners that your child modules can trigger, without bind the function would be fired with the context of the event and not the context of the parent .

Arrow Functions

An arrow function’s “this” takes the value of its enclosing context, and will “in most cases” retain that context no matter what it’s “this” is changed to. I put “In most cases” in quotes because there is one way around that issue that I will show you shortly. So, let’s see what we mean here:

        
            let arrow = () => this;
            console.log(arrow() == this);
            
            let newObj = {testVal: 1};
            console.log(arrow.apply(newObj) === this);
            console.log(arrow.bind(newObj)() === this);
        
    

Result:

        
            true
            true
            true
        
    

If it is returned from a function inside an object, it will return the context of the object:

        
            let obj = {
                    myFunc: function() {
                            return () => this;
                    }
            }
            
            console.log(obj.myFunc()());
            console.log(obj.myFunc().apply(this));
            console.log(obj.myFunc().bind(this)());
        
    

Result:

        
            { myFunc: [Function: myFunc] }
            { myFunc: [Function: myFunc] }
            { myFunc: [Function: myFunc] }
        
    

Changing the context of an arrow function

Before I claimed that there was a way around not being able to apply a new context to an arrow function. Well it is possible and we can achieve it, in the case of a function within an object that returns an arrow function, by applying a new "this" to the enclosing function, myFunc, instead of the arrow function itself as we did previously:

        
            let obj = {
                myFunc: function() {
                    return () => this;
                }
            }
            
            console.log(obj.myFunc()());
            console.log(obj.myFunc.bind(this)()());
        
    

Result:

        
            { myFunc: [Function: myFunc] }
            {}
        
    

So in this case the placement of the parenthesis matters, when the first set is right after myFunc like so: console.log(obj.myFunc().bind(this)()) we are binding to the function returned from the myFunc, and then calling that function with the set of parenthesis at the end. When we move the parenthesis to after the bind console.log(obj.myFunc.bind(this)()()), we are binding to the myFunc itself, then calling that function with the first set of parenthesis and, next, calling the function that myFunc returns with the second set. In this case since we changed the binding of the enclosing context, and since functions only exist in memory when they are called, we were able to change the context of the arrow function by changing the context of the function that returns it.

Well, that wraps it up for “this” in JS, I hope you enjoyed this post and came away less confused by “this” rather than more. I’d like to thank you for taking the time to read this post (my first!) and please upvote if you like it.

Sort:  

Congratulations @sovereignrk! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

You made your First Comment

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

By upvoting this notification, you can help all Steemit users. Learn how here!

The @OriginalWorks bot has determined this post by @sovereignrk to be original material and upvoted it!

ezgif.com-resize.gif

To call @OriginalWorks, simply reply to any post with @originalworks or !originalworks in your message!

Coin Marketplace

STEEM 0.21
TRX 0.25
JST 0.038
BTC 95583.13
ETH 3350.54
USDT 1.00
SBD 3.09