Learn Javascript this, Once and Forever

Learn Javascript this, Once and Forever

Tags
Web Dev
Javascript
Published
June 26, 2020
Author
Kavian Rabbani
As a Javascript developer, whether you are writing vanilla(pure) js, creating the next “React” yourself, or working with Vue, Angular, Svelte, or any other framework/library, you need to know this well.
As I talk to other developers, I sometimes notice that there is a big hole in their understanding of this. so I’m here to mention some of them and make you and this, good friends forever 😄
Let’s dive into some examples to start learning what this is and how it’s used.
function logThis() { console.log(this); } logThis();
What is the value logged?
The answer is, it depends 🙃 just like an answer to any other question.
You ask on what? Let me explain.
We can call a function in several ways, being:
  1. Normally, like logThis()
  1. Setting it as a method, on an object (e.g. named obj) and then calling it like obj.logThis()
  1. With the use of .call(), .apply() or .bind() methods on Function.prototype
  1. With new, like new logThis()
Here is a rule of thumb: How you call a function determines what this would be.
Let’s discuss it further 👇
  1. Normal invocation (with ())
    1. function logThis() { console.log(this); } logThis(); // window
      In this case, the answer is the global object, which is window in Browser and global in Node.
      There is a subtle point to be aware of though 🤏
      If we’re in strict mode the answer to example 1, is always undefined.
  1. Calling a function as a method of an object, like obj.method()
    1. function logThis() { console.log(this); } const obj = { value: 20, logMyThis: logThis, }; const obj2 = { value: 30, logMyThis: function () { console.log(this); }, }; obj.logMyThis(); // obj obj2.logMyThis(); // obj2
      See? There is no difference between the results above.
      It’s all about how we call each function.
      As we can see in the example above, there is no difference in how we assigned the method to our objects. the value of this depends on how we call that function, nothing more. When we call a function like foo.bar.baz(), this will be foo.bar.
      You might ask what if we have a method in an obj, and then we assign it to a variable and then call that new function?
      const obj = { value: 30, logMyThis: function () { console.log(this); }, }; const logThis2 = obj.logMyThis; logThis2(); // global object (window in browser) obj.logMyThis(); // obj
      It’s trivial where the function originally belongs.
      As you see, “all that’s important, is how function calls are done”.
  1. Using call(), apply() or bind()
    1. Generally speaking, bind(), call(), and apply() are used to modify the context (this) of a function.
      However, there is a subtle difference between bind() and call()/apply() 👇 bind() returns a new function, with the this that we set; whereas, call() and apply() do not return a new function. Instead, they call the original function with our desired context (this).
      Now that we know how call(), apply(), and bind() work, let’s talk about this in each one.
      function setValueAndLogThis(value) { this.value = value; console.log(this); } const myThis = { value: 20, }; setValueAndLogThis(30); // Logs window, when window.value === 30 setValueAndLogThis.call(myThis, 40); // Logs myThis, when myThis.value === 40 setValueAndLogThis.apply(myThis, [50]); // Logs myThis, when myThis.value === 50 const newLogger = setValueAndLogThis.bind(myThis); // setValueAndLogThis is not called // However, we have a new copy of setValueAndLogThis // with a hard-set `this` value bound to it which is myThis newLogger(60); // Logs myThis, when myThis.value === 60 console.log(myThis.value); // 60
      As we can see, we can change the context of a function both when we want to call it immediately (by call and apply), and when we want to call it later (using bind()). Keep in mind that whenever we use bind(), we are creating a function that we never can change its context, except for when we call this function by new keyword (which is a very exceptional case, which we'll cover later).
  1. Calling a function with new keyword
    1. The new keyword is a strong one. it rules over all of the above cases when we are talking about this. Whenever a function is called with new, regardless of how it’s written, the function starts to run with this equal to an empty object.
      If we don’t return this, it’s returned automatically. In case an object (non-primitive value) is returned from the function it will override the automatically returned this (as might have anticipated). However, the irony is if a primitive value (like string, number, null, and …) gets returned, it will be ignored and will result in this being returned actually.
      By now, you know everything about this when we are working with a normal function. But stay tuned 😄 there is more to learn.
      Things are a bit different speaking of arrow functions, though. First, we should know that an arrow function could not be called as a constructor function(with a new keyword). The second point is that we can never change this value of an arrow function. even by creating a new one with the help of bind(). And the third, last, and most important point is that the value of this for an arrow function is not determined by How we call it but is determined by *Where we declare it \first.\\
      there are two important words above: Where and First.
      By “where”, I mean the function scope (not to be confused with an object or block scope) in which we are creating that arrow function. let’s see an example:
      const obj = { value: 20, logMyThis: () => { console.log(this); }, }; obj.logMyThis(); // global object (window in browsers)
      When logMyThis method was declared, the main function of the app was running with this value of window or global. so when Javascript JIT looks at this arrow function expression, it behaves like the below:
      const obj = { value: 20, logMyThis: function () { console.log(this); }.bind(this), };
      It creates a new function bound to this in the main function(that is global object).
      Let’s check out a more complex example:
      function runInnerArrowFunction() { const obj = { value: 20, logMyThis: () => { console.log(this); }, }; obj.logMyThis(); } runInnerArrowFunction();
      As we learned in the very first example, by invoking runInnerArrowFunction this way, we are implicitly setting its this value to window.
      Then, it declares/defines a variable/object within itself.
      And consequently, creates a method inside of it just like below:
      const obj = { value: 20, logMyThis: (function() { console.log(this) }).bind(this); }
      Therefore, it logs the window object. And one step further:
      const customThis = { value: 20 }; runInnerArrowFunction.call(customThis); // customThis
      Now, you know that the way we call runInnerArrowFunction is determining the value of this for the inner arrow function.
      Now, let’s discuss the second important word: First
      Take a look at the example below:
      const obj = { value: 20, logMyThis: () => { console.log(this); }, }; // The this value of `logMyThis` is already bound to it // as `obj` and `obj.logMyThis` were being declared/defined above. // `this` will be be undefined in strict mode and // will reference to the global object in non-strict mode. function runOuterArrowFunction() { const logThis = obj.logMyThis; logThis(); } runOuterArrowFunction(); // window runOuterArrowFunction.call({ value: 100 }); // window
      As you can see there is no difference in How we call the function. Once this is bound, it will stay bound, forever. Remember, it’s one of the characteristics of arrow functions (not all functions).
 
Well, because classes are just syntactic sugar over constructor functions, we can say that the same rules apply to them.
And with that, we have covered all the cases of this in Javascript. I hope you enjoyed it and learned something new. In that case, please share it with your friends and colleagues 😃
Cheers! 🍻