Exploring JavaScript: My Journey with the this Keyword

joshydev

Sani Joshua

Posted on August 31, 2024

Exploring JavaScript: My Journey with the this Keyword

While working, I had the idea of using the this keyword in an object. So I thought, "Why not just do something like this?"

const a = {
  a: 3,
  b: this.a
};
Enter fullscreen mode Exit fullscreen mode

But, of course, that didn't work—it resulted in undefined. So, I turned to ChatGPT for a better solution, and it suggested:

const a = {
  a: 3,
};
a.b = a.a;
Enter fullscreen mode Exit fullscreen mode

Or, another option:

const a = {
  a: 3,
  b: (() => 3)(),
};
Enter fullscreen mode Exit fullscreen mode

I wasn't satisfied with either solution because I really wanted to use the this keyword. I insisted on finding a way, which led to a "this" (pun intended) discussion.

If you want to use this within an object to refer to another property in the same object, you can do it with a getter, like this:

const a = {
  a: 3,
  get b() {
    return this.a;
  },
};
Enter fullscreen mode Exit fullscreen mode

That was neat, but my curiosity kicked in, and I wondered if I could do the same in a function. I asked:

"What about this?"

const a = {
  a: 3,
  b: function() {
    return this.a;
  },
};
Enter fullscreen mode Exit fullscreen mode

or

const a = {
  a: 3,
  b() {
    return this.a;
  },
};
Enter fullscreen mode Exit fullscreen mode

But this solution required calling b() every time, which I didn’t like. I then thought, maybe a self-calling function would work: So I tried this:

const a = {
  a: 3,
  b: (function() {
    return this.a;
  })()
};
Enter fullscreen mode Exit fullscreen mode

This led to another "this" conversation because using this is such a hot topic.

In this case, the b function is immediately invoked when the object a is being created. However, the value of this inside the function isn't what you'd expect—it refers to the global object (window in browsers, global in Node.js) or is undefined in strict mode. That's why this.a ends up being undefined.

Arrow Functions and this
In JavaScript, the this keyword inside an arrow function doesn't work the same way as in regular functions. Instead, it captures this from the surrounding context when the arrow function is defined.

const obj = {
  value: 10,
  showValue: () =>  {
    console.log(this.value); // `this` refers to the global object (or `undefined` in strict mode)
  }
};
Enter fullscreen mode Exit fullscreen mode
const obj = {
  value: 10,
  showValue () {
    console.log(this.value); // `this` refers to the object `obj`
  }
};

obj.showValue(); // Outputs: 10
Enter fullscreen mode Exit fullscreen mode

IIFE and this
Since the function is invoked immediately and isn't part of the object a when it's invoked, this refers to the global object or is undefined in strict mode.

After gathering all this information, I realized that I had to use a getter to leverage this. But after writing a more complicated version of my code, I started to think about simplifying things. I asked the final question:

"How can I simplify this?"

const c = {
  x: true, // some operation that produces boolean
  y: false, // some other operation
  get a() {
    return this.x && this.y;
  },
};
Enter fullscreen mode Exit fullscreen mode

And it gave me this:

const x = false // some operation that produces boolean;
const y = true // some other operation;
const c = {
  a: x && y
};
Enter fullscreen mode Exit fullscreen mode

This brought me back to where I started. My thought was, that if the variables got longer, I'll end up creating both a long list of variables and a long object.

So we had "this" conversation.


What's wrong with getters?

There’s nothing inherently wrong with using getters. They’re actually quite useful, especially when you want to create a property that’s calculated based on other data. However, in your case, using getters might not be necessary or could introduce subtle issues, depending on how they’re used.

When Getters are Useful:

  • Lazy Evaluation: They only calculate the value when it’s accessed, which can save computation time.
  • Encapsulation: They provide a clean interface for accessing calculated properties without exposing the implementation details.

Example

const x = {
  _a: 3,  // Internal property to store the value
  get a() {
    return this._a;
  },
  set a(value) {
    this._a = value;
    // You can also define what should happen when 'a' is set
  },
  get b() {
    return this._a;  // Return the value of 'a'
  },
  set b(value) {
    this._a = value;  // Set 'a' when 'b' is set
  }
};
Enter fullscreen mode Exit fullscreen mode

Potential Issues with Getters:

  • Performance: If the logic is complex, it could introduce performance overhead because the value is recalculated every time it’s accessed.
  • Misleading: It might not be obvious that a property involves computation, leading to confusion.
  • Context (this) Sensitivity: Getters rely on the correct context (this). If the context isn’t what you expect, it could lead to bugs.

When Not to Use Getters:

  • Static Data: If the data doesn’t change, using getters adds unnecessary complexity.
  • Complex Computations: For complex computations that are accessed multiple times, it might be better to store the result in a variable to avoid recalculating.

So from my previous example using get, x, and y was not exposed which makes it a good solution if I wanted encapsulation. My concern was mainly about performance, so just for fun, I asked:

"What’s the difference between accessing a variable in memory vs. performing a computation or operation, e.g., a mathematical one, in terms of performance?"

Summary:

  • Memory Access: Fast and predictable, making it cheap in terms of performance.
  • Computation: Can range from slightly slower to significantly slower, depending on the complexity. It consumes more CPU resources and can introduce unpredictability in execution time.

I ended up spending 30 minutes on a 3-minute task, but I learned a few new concepts like getters and setters in objects, the scope of this in an IIFE, and the this keyword in various instances, along with a general understanding of recomputation vs. memory access.

Conclusion on this:

Here’s a quick reference for how the this keyword behaves in various scenarios:

  • Global Context:
    • Non-strict mode: this refers to the global object (window in browsers).
    • Strict mode: this is undefined.
  • Object Method: this refers to the object that owns the method.
  • Constructor Function: this refers to the newly created instance.
  • Class Method: this refers to the class instance.
  • Arrow Function: this is lexically bound to the surrounding context.
  • Event Handler (DOM): this refers to the element that received the event.
  • Explicit Binding (call, apply, bind): this is explicitly set to the first argument provided.
  • Inline Event Handler (HTML): this refers to the DOM element that is the target of the event.
  • Global Arrow Function (non-strict): this refers to the global object.
  • In an IIFE:
    • Non-strict mode: this refers to the global object.
    • Strict mode: this is undefined.
  • Class Static Method: this refers to the class itself.
  • In setTimeout or setInterval:
    • Traditional function: this refers to the global object.
    • Arrow function: this is inherited from the surrounding context.
  • Module Scope (ES6 Modules): this is undefined in module scope.
  • Proxy Handler: this inside a proxy handler refers to the proxy itself.
  • With new keyword in Function: this refers to the newly created object.

I understand some of this, and I’ll have to look up the rest from time to time. This article serves as a reference so I don’t have to search all over again.

If you like this kind of article about how I learn, give it a like!

NB: This article was mostly generated with AI.

💖 💪 🙅 🚩
joshydev
Sani Joshua

Posted on August 31, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related