Exploring JavaScript: My Journey with the this Keyword
Sani Joshua
Posted on August 31, 2024
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
};
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;
Or, another option:
const a = {
a: 3,
b: (() => 3)(),
};
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;
},
};
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;
},
};
or
const a = {
a: 3,
b() {
return this.a;
},
};
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;
})()
};
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)
}
};
const obj = {
value: 10,
showValue () {
console.log(this.value); // `this` refers to the object `obj`
}
};
obj.showValue(); // Outputs: 10
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;
},
};
And it gave me this:
const x = false // some operation that produces boolean;
const y = true // some other operation;
const c = {
a: x && y
};
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
}
};
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
isundefined
.
- Non-strict mode:
-
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
isundefined
.
- Non-strict mode:
-
Class Static Method:
this
refers to the class itself. -
In
setTimeout
orsetInterval
:- Traditional function:
this
refers to the global object. - Arrow function:
this
is inherited from the surrounding context.
- Traditional function:
-
Module Scope (ES6 Modules):
this
isundefined
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.
Posted on August 31, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.