The mystery of “this” in JavaScript.
Tags: javascript, webdev, programming, beginners
Published: 2023-04-01 11:06:30

<blockquote>
<h1 id="heading-javascript-like-a-box-of-gems">JavaScript, like a box of gems</h1>
</blockquote>
<p>If you are into JavaScript, then you cannot deny the fact that <em>this</em> keyword is very confusing 🤨 when you are writing code, especially when you read others’ code.</p>
<p>In this blog, we will understand how to use <em>this</em> correctly ✔️ and also the scenarios where it is most misunderstood. </p>
<p>In the previous  <a target="_blank" href="https://mohit-codes.hashnode.dev/differences-between-arrow-function-and-regular-function-in-javascript">blog post</a>, I mentioned that function invocations in JavaScript can be split into 4 types :</p>
<ul>
<li>function invocation</li>
<li>method invocation</li>
<li>constructor invocation</li>
<li>indirect invocation  </li>
</ul>
<p>Each function invocation defines its context differently.</p>
<blockquote>
<p><em>The key to understanding this keyword is to understand how function invocation affects the context.</em></p>
</blockquote>
<p>Let's become familiar with some terms before getting started :</p>
<ul>
<li><strong>Invocation</strong> - Invoking a function means running the code that makes up its body, or simply calling it.</li>
<li><strong>Context</strong> - The value of <em>this</em> within function body.</li>
<li><strong>Scope</strong> - A function's scope is the set of variables and functions it can access.</li>
</ul>
<h2 id="heading-1-function-invocation">1. Function Invocation</h2>
<p>A function invocation occurs when an expression that evaluates to a function object is followed by an open parenthesis (, a comma separated list of arguments expressions, and a close parenthesis).</p>
<p>Here is a simple example of function invocation:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">square</span>(<span class="hljs-params">x</span>)</span>{
    <span class="hljs-keyword">return</span> x<span class="hljs-operator">*</span>x;
}
<span class="hljs-comment">// Function invocation</span>
square(<span class="hljs-number">5</span>);
</code></pre><p><code>square(5)</code> is a function invocation, <code>square</code> evaluates to a function object, followed by a pair of parentheses and the argument 5.</p>
<p>Remember the distinction between function invocation and method invocation. For example, <code>['a','b'].join('')</code> is not a function invocation, but a method invocation.</p>
<h3 id="heading-11-this-in-a-function-invocation">1.1 this in a function invocation</h3>
<blockquote>
<p>In a function invocation, this refers to the global object.</p>
</blockquote>
<p>The global object is determined by the execution environment. The window object is the global object in a browser.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">square</span>(<span class="hljs-params">x</span>)</span>{
    console.log(<span class="hljs-built_in">this</span>);  <span class="hljs-comment">// same window object</span>
    console.log(<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> window);  <span class="hljs-comment">// true</span>
    <span class="hljs-keyword">return</span> x<span class="hljs-operator">*</span>x;
}
<span class="hljs-comment">// Function invocation</span>
square(<span class="hljs-number">5</span>);
console.log(<span class="hljs-built_in">this</span> , <span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> window); <span class="hljs-comment">// logs window object and true</span>
</code></pre><p>In NodeJS, <em>this</em> is the current <code>module.exports</code> object, not the global object, but inside a function, <em>this</em> will point to the global object, or we can use the global keyword to access it.</p>
<pre><code>console.log(<span class="hljs-built_in">this</span>);    <span class="hljs-comment">// logs {}</span>

module.exports.num <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;

console.log(<span class="hljs-built_in">this</span>);   <span class="hljs-comment">// logs { num:5 }</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">square</span>(<span class="hljs-params">x</span>)</span>{
  console.log(<span class="hljs-built_in">this</span>,<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> global);    <span class="hljs-comment">// logs global object and true</span>
}

console.log(global);  <span class="hljs-comment">// logs global object</span>
</code></pre><p>Note: In strict mode,<em> this </em>is undefined</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">square</span>(<span class="hljs-params">x</span>)</span>{
<span class="hljs-meta">    'use strict'</span>;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>);  <span class="hljs-comment">// undefined</span>
    <span class="hljs-keyword">return</span> x*x;
}
<span class="hljs-comment">// Function invocation</span>
square(<span class="hljs-number">5</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span> , <span class="hljs-built_in">this</span> === <span class="hljs-built_in">window</span>); <span class="hljs-comment">// logs window object and true</span>
</code></pre><h3 id="heading-12-this-in-inner-function">1.2 this in inner function</h3>
<p> ❗️  An easy trap with function invocation is assuming that <em>this</em> is the same in an inner function as in the outer function.</p>
<p>✔️ The context of an inner function (except the arrow function) is entirely determined by its own invocation type, not by the context of the outer function.</p>
<pre><code>const car <span class="hljs-operator">=</span> {
  distance: <span class="hljs-number">200</span>,
  time: <span class="hljs-number">2</span>,
  speed: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
      console.log(<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> car) <span class="hljs-comment">// true</span>
      console.log(<span class="hljs-built_in">this</span>.distance <span class="hljs-operator">/</span> <span class="hljs-built_in">this</span>.time) <span class="hljs-comment">// 100</span>
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculate</span>(<span class="hljs-params"></span>)</span>{
        console.log(<span class="hljs-built_in">this</span>,<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> window)  <span class="hljs-comment">// window object and true</span>
        console.log(<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> car) <span class="hljs-comment">// false</span>
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.distance <span class="hljs-operator">/</span> <span class="hljs-built_in">this</span>.time;  <span class="hljs-comment">// error</span>
    }
   <span class="hljs-keyword">return</span> calculate(); <span class="hljs-comment">// function invocation</span>
  }
};
car.speed();
</code></pre><p><code>car.speed()</code> is a method invocation on an object thus <em>this</em> equals car object. <code>calculate()</code> function is defined inside <code>speed()</code>, so you might expect to have <em>this</em> as car object inside <code>calculate()</code> too.</p>
<p><code>calculate()</code> is a function invocation, not method invocation, thus here this refers to the global object window or undefined in strict mode.</p>
<p>Invocation of <code>car.speed()</code> does not produce the expected result of 200 / 2 = 100.</p>
<p>👍To solve the problem, <code>calculate()</code> function must execute with the same context as the <code>car.speed()</code> method, to access <code>this.distance</code> and <code>this.time</code> properties.</p>
<p>One solution is to use <code>calculate.call(this)</code> to manually set the context of inner function as same as that of outer, another much better solution is to use arrow function so that context of inner function is resolved lexically.</p>
<pre><code>const car <span class="hljs-operator">=</span> {
  distance: <span class="hljs-number">200</span>,
  time: <span class="hljs-number">2</span>,
  speed: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
      console.log(<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> car) <span class="hljs-comment">// true</span>
      const calculate <span class="hljs-operator">=</span> () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
         console.log(<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> car)  <span class="hljs-comment">// car object and true</span>
         <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.distance <span class="hljs-operator">/</span> <span class="hljs-built_in">this</span>.time; 
      }
     <span class="hljs-keyword">return</span> calculate();
    }
};
car.speed();  <span class="hljs-comment">// 100</span>
</code></pre><h2 id="heading-2-method-invocation">2. Method invocation</h2>
<blockquote>
<p>A <strong>method</strong> is a function stored in an object's property.</p>
</blockquote>
<pre><code>const greet <span class="hljs-operator">=</span>  {
  english: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
         console.log(<span class="hljs-string">"Hello"</span>);
   },
  hindi: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
         console.log(<span class="hljs-string">"Namaste"</span>);
   }
};
</code></pre><p><code>english</code> and <code>hindi</code> are methods of the <code>greet</code> object, and we can invoke these methods using the property accessors <code>greet.english()</code> and <code>greet.hindi()</code>.</p>
<h3 id="heading-21-this-in-a-method-invocation">2.1 this in a method invocation</h3>
<blockquote>
<p>In a method invocation, this refers to the object that owns the method.</p>
</blockquote>
<pre><code>const greetUser <span class="hljs-operator">=</span> {
    name: <span class="hljs-string">"Dan"</span>,
    english: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        console.log(<span class="hljs-built_in">this</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> greetUser);   <span class="hljs-comment">// true</span>
        console.log(<span class="hljs-string">"Hello "</span> <span class="hljs-operator">+</span> <span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>);  <span class="hljs-comment">// logs Hello Dan</span>
    },
    hindi: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        console.log(<span class="hljs-string">"Namaste "</span> <span class="hljs-operator">+</span> <span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>);
    }
};
greetUser.engilsh();   <span class="hljs-comment">// method invocation</span>
</code></pre><h3 id="heading-22-separating-method-from-the-object">2.2 separating method from the object</h3>
<p>❗️ A method can be extracted from an object into a separate variable. when the detached method is invoked, you might expect <em>this</em>  to be the object in which the method was defined.</p>
<p>✔️ If the method is called without an object, then a function invocation occurs, where <em>this</em> is either the global object window or undefined in strict mode.</p>
<pre><code>const greetUser <span class="hljs-operator">=</span> {
    userName: <span class="hljs-string">"Dan"</span>,
    dogName:<span class="hljs-string">"milo"</span>,
    english: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        console.log(`${<span class="hljs-built_in">this</span>.userName} loves ${<span class="hljs-built_in">this</span>.dogName}`);
    },
    hindi: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        console.log(<span class="hljs-string">"Namaste "</span> <span class="hljs-operator">+</span> <span class="hljs-built_in">this</span>.<span class="hljs-built_in">name</span>);
    }
};
const greet <span class="hljs-operator">=</span> greetUser.english;
greet();   <span class="hljs-comment">// function invocation logs "undefined loves undefined"</span>
</code></pre><p>When the separated method <code>english</code> is invoked as a function, this is the global object or undefined in strict mode (not the <code>greetUser</code> object), and since the global object does not contain such properties, it logs undefined.</p>
<p>👍To solve the context issue, we can bind the function with the object using the<code>.bind()</code> method.</p>
<pre><code>const greet <span class="hljs-operator">=</span> greetUser.english.bind(greetUser);
greet();  <span class="hljs-comment">// logs "Dan loves milo"</span>
</code></pre><h1 id="heading-conclusion">Conclusion</h1>
<p>By now, I think you have a pretty good understanding of how function invocation impacts the context, and what common pitfalls to avoid.</p>
<p>🔜 In upcoming posts, I'll be writing about how constructor and indirect invocation impacts context as well as some common misunderstood cases.</p>
<p>🙏 Thank you very much for reading this article. I hope you found it useful and if you did, please do share it with your friends or anyone who may find this interesting. ✨</p>
</br><p style="color:#757CF9;">This article is published w/ <a target="_blank" href="https://scattr.io?ref=dev">Scattr  ↗️</a></p>