JavaScript this Keyword Explained Simply

If you've written JavaScript for a while, you've probably seen this:
this.name
It looks simple.
Then you put it inside a function and suddenly this becomes undefined, a global object, or something completely different.
Why?
Because one of the most common explanations of this is also one of the most misleading:
"
thisrefers to the current object."
That's only sometimes true.
A much better mental model is:
For regular functions,
thisis determined by how the function is called.
And there's one major exception:
Arrow functions don't have their own
this; they inherit it from their surrounding scope.
Once you understand those two ideas, most this behavior becomes predictable.
The Core Mental Model
For a regular function, first look at the call:
How was the function called?
│
┌─────┼──────────┐
↓ ↓ ↓
object. fn() new Fn()
method()
│ │ │
↓ ↓ ↓
object depends new object
on mode
But for an arrow function:
Arrow function
│
↓
Doesn't create its own `this`
│
↓
Uses surrounding `this`
This distinction explains most of JavaScript's this.
this in an Object Method
The easiest case:
const user = {
name: "Maaz",
greet() {
console.log(this.name);
}
};
user.greet();
The function is called as:
user.greet();
Therefore:
user.greet()
↓
this = user
So:
this.name
is effectively:
user.name
Output:
Maaz
This is why the common pattern:
object.method();
usually gives:
this → object
this Is Not Where the Function Was Defined
Here's the important part.
const user = {
name: "Maaz",
greet() {
console.log(this.name);
}
};
user.greet();
works because of the call site.
But:
const greet = user.greet;
greet();
is a different call.
The function is now called as:
greet();
not:
user.greet();
The original receiver is gone.
So don't think:
"
thisbelongs to the object where the function was created."
Instead ask:
"How is this function being called right now?"
Regular Functions
The same this rules apply whether you create a regular function using a declaration:
function greet() {
console.log(this);
}
or a function expression:
const greet = function () {
console.log(this);
};
Both are regular functions.
Their this is determined by how they're invoked.
For example:
const user = {
name: "Maaz",
greet: function () {
console.log(this.name);
}
};
user.greet();
Here:
this → user
The important distinction isn't:
function declaration vs function expression
It's:
regular function vs arrow function
this in a Normal Function Call
Consider:
"use strict";
function showThis() {
console.log(this);
}
showThis();
There is no object before the function call.
It's simply:
showThis();
In strict mode:
this → undefined
In non-strict classic script code, a normal function call can default this to the global object.
So:
Regular function
│
├── strict mode
│ ↓
│ undefined
│
└── non-strict
↓
global object
This is why:
"
thisalways means the caller"
is not a completely accurate rule.
It's better to say:
Regular functions get
thisfrom their invocation pattern.
Global this
Top-level this is a separate case from function this.
In a traditional browser script:
console.log(this === window);
generally gives:
true
But ES modules behave differently:
// module.js
console.log(this);
At the top level of an ES module:
this → undefined
Node.js can also differ depending on whether you're using CommonJS or ES modules.
So don't memorize:
this = window
as a universal JavaScript rule.
Instead ask:
Where is this code running?
Script?
Module?
Function?
Method?
Arrow Functions: The Big Exception
Now consider:
const user = {
name: "Maaz",
greet: () => {
console.log(this.name);
}
};
user.greet();
You might expect:
this → user
But arrow functions don't work that way.
An arrow function doesn't create its own this.
Instead, it inherits this from its surrounding lexical scope.
Think:
Arrow function
│
↓
No own `this`
│
↓
Look at surrounding scope
│
↓
Use that `this`
Therefore, an arrow function used as an object method does not automatically get the object as this.
Regular Function vs Arrow Function
Compare:
const user = {
name: "Maaz",
regular() {
console.log(this.name);
},
arrow: () => {
console.log(this.name);
}
};
user.regular();
user.arrow();
The regular method:
user.regular()
↓
this = user
The arrow:
user.arrow()
↓
arrow has no own this
↓
uses surrounding this
This is why, when an object method needs dynamic this, the normal method syntax is usually the better choice:
const user = {
name: "Maaz",
greet() {
console.log(this.name);
}
};
rather than:
const user = {
name: "Maaz",
greet: () => {
console.log(this.name);
}
};
Nested Functions: Where Things Get Interesting
Consider:
const user = {
name: "Maaz",
greet() {
console.log(this.name);
function inner() {
console.log(this.name);
}
inner();
}
};
user.greet();
The outer method is called as:
user.greet();
so:
greet()
↓
this = user
But inner() is called separately:
inner();
It's another regular function call.
It does not automatically inherit the outer this.
Now change it to an arrow:
const user = {
name: "Maaz",
greet() {
const inner = () => {
console.log(this.name);
};
inner();
}
};
user.greet();
Now the arrow inherits this from greet():
user.greet()
↓
this = user
↓
inner arrow
↓
inherits this
↓
user
This is one of the most practical reasons arrow functions are useful.
this in Callbacks
The same concept appears constantly in asynchronous JavaScript.
This can cause problems:
const user = {
name: "Maaz",
greet() {
setTimeout(function () {
console.log(this.name);
}, 1000);
}
};
The callback is a separate regular function.
It doesn't automatically inherit the outer method's this.
An arrow callback solves that:
const user = {
name: "Maaz",
greet() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
};
Now:
greet()
↓
this = user
↓
arrow callback
↓
inherits this
↓
user
This pattern appears frequently with:
setTimeout
Promises
array callbacks
event handlers
async code
this in Event Handlers
Browser event handlers are another important case.
With a regular function:
button.addEventListener("click", function () {
console.log(this);
});
the browser sets this to the element handling the event.
So:
regular event listener
↓
this = currentTarget
With an arrow:
button.addEventListener("click", () => {
console.log(this);
});
the arrow doesn't get the button as this.
It inherits this from the surrounding scope.
That's why arrow-based event handlers commonly use:
button.addEventListener("click", (event) => {
console.log(event.currentTarget);
});
instead.
Also remember:
this / event.currentTarget
↓
element whose listener is handling the event
event.target
↓
element where the event actually originated
These aren't always the same element.
call(), apply(), and bind()
JavaScript gives you explicit control over this.
call()
function greet() {
console.log(this.name);
}
const user = {
name: "Maaz"
};
greet.call(user);
Now:
this → user
call() executes the function immediately.
apply()
apply() works similarly but accepts arguments as an array:
function introduce(age, city) {
console.log(this.name, age, city);
}
introduce.apply(user, [23, "Multan"]);
Think:
call(obj, arg1, arg2)
apply(obj, [arg1, arg2])
bind()
bind() doesn't immediately execute the function.
It creates a new function with this bound:
const boundGreet = greet.bind(user);
boundGreet();
Now:
boundGreet()
↓
this = user
This is especially useful when passing object methods as callbacks.
Can call() Change Arrow Function this?
No.
For example:
const greet = () => {
console.log(this);
};
greet.call(user);
call() cannot replace an arrow function's lexical this.
The same applies to:
apply()
bind()
So:
Regular function
call/apply/bind → can control this
Arrow function
call/apply/bind → cannot replace lexical this
this With new
Now consider a constructor function:
function Student(name, age) {
this.name = name;
this.age = age;
}
const student = new Student("Maaz", 23);
With new, JavaScript creates a new object and uses that object as this.
A useful simplified model is:
new Student(...)
│
↓
create new object
│
↓
link to Student.prototype
│
↓
run constructor with this = object
│
↓
return object
Therefore:
student.name
gives:
Maaz
The same principle is used by classes:
class Student {
constructor(name) {
this.name = name;
}
}
const student = new Student("Maaz");
Inside the constructor:
this → newly created Student instance
this in Class Methods
Consider:
class Student {
constructor(name) {
this.name = name;
}
introduce() {
console.log(this.name);
}
}
const student = new Student("Maaz");
student.introduce();
Because the method is called as:
student.introduce();
we get:
this → student
But if we extract it:
const introduce = student.introduce;
introduce();
the method loses its receiver.
Since class methods are strict-mode functions:
this → undefined
This is a common source of bugs when passing class methods as callbacks.
Static Methods
Static methods belong to the class rather than an instance:
class MathHelper {
static square(number) {
return number * number;
}
}
MathHelper.square(5);
Inside the static method:
this → MathHelper
So:
Instance method
student.introduce()
↓
this = student
Static method
MathHelper.square()
↓
this = MathHelper
Getters and Setters
this also works inside getters and setters:
const user = {
firstName: "Maaz",
lastName: "Hafeez",
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(user.fullName);
Here this refers to the object on which the getter is accessed.
Setters work similarly:
const user = {
_name: "",
set name(value) {
this._name = value;
}
};
user.name = "Maaz";
So this isn't limited to ordinary methods.
A Powerful Example: Method Borrowing
Regular functions can be reused with different objects.
function introduce() {
console.log(`My name is ${this.name}`);
}
const user1 = {
name: "Maaz"
};
const user2 = {
name: "Ali"
};
introduce.call(user1);
introduce.call(user2);
Output:
My name is Maaz
My name is Ali
Same function.
Different this.
That's the power of JavaScript's dynamic function context.
The this Debugging Checklist
When this behaves unexpectedly, don't guess.
Ask these questions:
1. Is it an arrow function?
If yes:
It doesn't have its own this.
Look at the surrounding scope.
2. Is it called with new?
new Constructor()
Then:
this → new object
3. Is call, apply, or bind involved?
If it's a regular function, they can explicitly control this.
4. Is it called as object.method()?
Then:
this → object
5. Was the method extracted?
Look for:
const fn = object.method;
or:
const { method } = object;
The original receiver may have been lost.
6. Is it a normal function call?
fn();
Then check strict mode and the runtime.
This checklist is far more useful than memorizing isolated examples.
The this Cheat Sheet
| Situation | this |
|---|---|
obj.method() |
obj |
fn() in strict mode |
undefined |
fn() in non-strict classic code |
Global object |
fn.call(obj) |
obj |
fn.apply(obj) |
obj |
fn.bind(obj) |
Bound to obj |
new Fn() |
New object |
| Arrow function | Lexical this |
Arrow + call/apply/bind |
Lexical this remains |
| Regular DOM listener | currentTarget |
| Arrow DOM listener | Lexical this |
| ES module top-level | undefined |
| Classic browser script top-level | Global object |
Conclusion
You don't need to memorize dozens of unrelated rules.
Start with this:
this
│
┌────────┴────────┐
│ │
Regular function Arrow function
│ │
↓ ↓
Check the call Check outside
│ │
┌──────┼──────┐ │
↓ ↓ ↓ ↓
obj. new call/ surrounding
method() apply/ this
bind
│ │ │
↓ ↓ ↓
object new explicit
object value
And remember the two rules that matter most:
Regular functions get their
thisfrom how they are called.
Arrow functions don't have their own
this; they inherit it from their surrounding scope.
Once you start reading JavaScript by looking at the function type + call site, this stops looking random.
It becomes predictable.
Quick Recap
object.method()
↓
this = object
fn()
↓
strict → undefined
non-strict → global object
fn.call(obj)
fn.apply(obj)
fn.bind(obj)
↓
this = obj
new Fn()
↓
this = new object
arrow function
↓
this = surrounding lexical this
The next time you see:
this
don't ask:
"What object is this inside?"
Ask:
"What kind of function is this, and how was it called?"
That question is the key to understanding JavaScript's this.




