The this keyword is one of the most important concepts in JavaScript.
It is also one of the concepts that often confuses beginners because the value of this can change depending on how a function is called.
A useful way to think about this is:
Who is calling this function?
Example:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
user.greet();
Output:
Samir
Here:
user.greet()
↓
this = user
↓
this.name
↓
"Samir"
this?this is a special keyword that refers to a value associated with the current function invocation.
For normal functions, its value is generally determined by how the function is called.
Example:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
user.greet();
Because greet() is called through user:
user.greet()
inside the method:
this
↓
user
Therefore:
this.name
means:
user.name
Output:
Samir
this Important?this is commonly used with:
You will see this frequently when working with object-oriented JavaScript.
this in Different ContextsThe value of this depends on the situation.
We will look at:
call()apply()bind()this in Global ContextIn a traditional browser script, top-level this refers to the global window object.
console.log(this);
Typically:
Window
Visual:
Browser
↓
Global Object
↓
window
So in a classic browser script:
this === window
is typically:
true
However, this is different inside ES modules.
For example:
<script type="module" src="app.js"></script>
At the top level of an ES module:
console.log(this);
Output:
undefined
So remember:
Classic Browser Script
↓
Top-level this = window
ES Module
↓
Top-level this = undefined
Modern frontend projects commonly use ES modules.
this Inside Object MethodsA method is a function stored inside an object.
Example:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
user.greet();
Output:
Samir
Why?
Because the function was called like this:
user.greet();
Therefore:
this = user
Visual:
user.greet()
↓
Who called greet?
↓
user
↓
this = user
const car = {
brand: "Toyota",
showBrand() {
console.log(this.brand);
}
};
car.showBrand();
Output:
Toyota
Here:
car.showBrand()
↓
this = car
↓
this.brand
↓
"Toyota"
thisThe same function can produce different results depending on how it is called.
function greet() {
console.log(this.name);
}
const user1 = {
name: "Samir",
greet
};
const user2 = {
name: "Ram",
greet
};
Call:
user1.greet();
Output:
Samir
Call:
user2.greet();
Output:
Ram
The function itself did not change.
What changed?
Who called the function
Therefore:
user1.greet()
↓
this = user1
user2.greet()
↓
this = user2
This is an important rule for understanding this.
this in Regular FunctionsConsider a normal function:
function greet() {
console.log(this);
}
greet();
There is no object before the function call:
greet()
In a classic browser script running in non-strict mode, this is typically:
window
Strict mode changes this behavior.
"use strict";
function greet() {
console.log(this);
}
greet();
Output:
undefined
So:
Regular Function Call
Non-Strict Classic Script
↓
this = window
Strict Mode
↓
this = undefined
Modern JavaScript modules are automatically strict mode.
this.name Can FailExample:
"use strict";
function showName() {
console.log(this.name);
}
showName();
Here:
this = undefined
Trying to access:
this.name
can therefore cause:
TypeError
This is why it is important to understand the function's calling context.
this Inside Nested FunctionsConsider:
const user = {
name: "Samir",
greet() {
function inner() {
console.log(this);
}
inner();
}
};
user.greet();
You might expect:
this = user
inside inner().
But that does not happen.
Why?
Because inner() is called as a normal function:
inner();
It is not called as:
user.inner();
Therefore, in strict mode:
this = undefined
In a classic non-strict browser script:
this = window
user.greet()
↓
this = user
Inside greet():
inner()
↓
Normal Function Call
↓
this = undefined
(strict mode)
The outer method's this is not automatically passed into a nested regular function.
this in Arrow FunctionsArrow functions behave differently.
Arrow functions do not create their own this.
Instead, they inherit this from the surrounding lexical scope.
Example:
const user = {
name: "Samir",
greet() {
const inner = () => {
console.log(this.name);
};
inner();
}
};
user.greet();
Output:
Samir
Why?
user.greet()
↓
this = user
↓
Arrow Function
↓
Uses surrounding this
↓
this = user
const user = {
name: "Samir",
greet() {
function test() {
console.log(this);
}
test();
}
};
user.greet();
In strict mode:
undefined
const user = {
name: "Samir",
greet() {
const test = () => {
console.log(this.name);
};
test();
}
};
user.greet();
Output:
Samir
The difference:
Regular Function
↓
Gets this from how it is called
Arrow Function
↓
Inherits this from surrounding scope
Consider:
const user = {
name: "Samir",
greet: () => {
console.log(this.name);
}
};
user.greet();
You might expect:
Samir
But that does not happen.
Why?
Because the arrow function does not receive user as its own this.
user
│
├── name
│
└── greet → Arrow Function
↓
No Own this
↓
Uses Outer this
In modern ES modules, the surrounding top-level this is:
undefined
So using arrow functions as object methods when you need the object as this is usually incorrect.
Instead of:
const user = {
name: "Samir",
greet: () => {
console.log(this.name);
}
};
Use:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
Now:
user.greet();
Output:
Samir
this in Event HandlersJavaScript frequently uses this with DOM events.
HTML:
<button id="btn">
Click Me
</button>
JavaScript:
const btn =
document.getElementById("btn");
btn.addEventListener(
"click",
function () {
console.log(this);
}
);
When the button is clicked, this refers to the element on which the listener was registered.
Output:
<button id="btn">
Click Me
</button>
Conceptually:
Button Clicked
↓
Event Handler
↓
this = button
thisbtn.addEventListener(
"click",
function () {
this.textContent =
"Clicked";
}
);
After clicking:
Click Me
↓
Clicked
Now consider:
btn.addEventListener(
"click",
() => {
console.log(this);
}
);
The arrow function does not get the button as its own this.
Instead:
Arrow Function
↓
Uses Surrounding this
In an ES module, top-level this is:
undefined
So do not rely on this inside an arrow event handler to access the element.
event.currentTargetA clearer modern approach is often:
btn.addEventListener(
"click",
event => {
console.log(
event.currentTarget
);
}
);
This directly gives the element whose event listener is running.
Example:
btn.addEventListener(
"click",
event => {
event.currentTarget
.textContent =
"Clicked";
}
);
This avoids confusion around this.
Sometimes we want to manually decide what this should refer to.
JavaScript provides three important methods:
call()
apply()
bind()
These allow us to control this.
call()call() invokes a function immediately while explicitly setting this.
Syntax:
functionName.call(
object,
arg1,
arg2
);
Example:
function greet() {
console.log(this.name);
}
const user = {
name: "Samir"
};
greet.call(user);
Output:
Samir
Visual:
greet.call(user)
↓
Set this
↓
this = user
↓
Execute greet()
call() with Argumentsfunction greet(city) {
console.log(
this.name,
city
);
}
const user = {
name: "Samir"
};
greet.call(
user,
"Jhapa"
);
Output:
Samir Jhapa
Here:
user
↓
Becomes this
"Jhapa"
↓
Becomes city
call()function introduce(
city,
country
) {
console.log(
this.name,
city,
country
);
}
const user = {
name: "Samir"
};
introduce.call(
user,
"Jhapa",
"Nepal"
);
Output:
Samir Jhapa Nepal
apply()apply() works similarly to call().
The main difference is how arguments are passed.
call():
greet.call(
user,
"Jhapa",
"Nepal"
);
apply():
greet.apply(
user,
[
"Jhapa",
"Nepal"
]
);
The arguments are passed as an array or array-like value.
apply() Examplefunction greet(
city,
country
) {
console.log(
this.name,
city,
country
);
}
const user = {
name: "Samir"
};
greet.apply(
user,
[
"Jhapa",
"Nepal"
]
);
Output:
Samir Jhapa Nepal
call() vs apply()The main difference:
call()
↓
Arguments separately
apply()
↓
Arguments in an array-like value
Example:
greet.call(
user,
"Jhapa",
"Nepal"
);
vs:
greet.apply(
user,
[
"Jhapa",
"Nepal"
]
);
Both execute the function immediately.
bind()bind() is different.
It does not execute the function immediately.
Instead, it returns a new function with this bound to a specific value.
Example:
function greet() {
console.log(this.name);
}
const user = {
name: "Samir"
};
const boundFunction =
greet.bind(user);
Nothing has executed yet.
Now:
boundFunction();
Output:
Samir
Visual:
greet.bind(user)
↓
Create New Function
↓
this fixed to user
↓
boundFunction()
↓
Execute Later
bind() is UsefulConsider:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
Calling normally:
user.greet();
Output:
Samir
But:
const fn =
user.greet;
fn();
The method has been separated from the object.
Originally:
user.greet()
↓
this = user
Now:
fn()
↓
No user object before the call
↓
this is no longer user
In strict mode, this is:
undefined
this with bind()const fn =
user.greet.bind(user);
fn();
Output:
Samir
Now:
bind(user)
↓
this = user
↓
Even when function is called later
Consider:
const button = {
label: "Save",
click() {
console.log(this.label);
}
};
Calling directly:
button.click();
Output:
Save
But passing the method to a timer:
setTimeout(
button.click,
1000
);
The method is passed as a standalone callback.
It is no longer called as:
button.click();
Therefore, it loses its original receiver.
bind()setTimeout(
button.click.bind(button),
1000
);
Output after one second:
Save
Because:
button.click
↓
bind(button)
↓
this fixed to button
↓
setTimeout
↓
Function Executes
↓
this.label = "Save"
call() vs apply() vs bind()| Method | Executes Immediately | Arguments | Returns |
|---|---|---|---|
call() |
Yes | Separate arguments | Function result |
apply() |
Yes | Array / array-like arguments | Function result |
bind() |
No | Separate arguments | New bound function |
Easy way to remember:
call()
↓
Call Now
apply()
↓
Call Now
With Array-like Arguments
bind()
↓
Create Function
Call Later
this in ClassesClasses frequently use this.
Example:
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(
`Hello ${this.name}`
);
}
}
Create an object:
const user =
new User("Samir");
user.greet();
Output:
Hello Samir
Here:
new User("Samir")
↓
Creates Instance
↓
this = new instance
So:
this.name = name;
stores the value on that instance.
const user1 =
new User("Samir");
const user2 =
new User("Ram");
Call:
user1.greet();
Output:
Hello Samir
Call:
user2.greet();
Output:
Hello Ram
Because:
user1.greet()
↓
this = user1
user2.greet()
↓
this = user2
Consider:
const user = {
name: "Samir",
greet: () => {
console.log(this.name);
}
};
user.greet();
Will it print:
Samir
?
No.
Why?
Because:
Arrow Function
↓
Does Not Create Own this
↓
Does Not Use user as this
↓
Uses Surrounding this
In an ES module, surrounding top-level this is:
undefined
So this code may fail when trying to access:
this.name
The correct object method is:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
Now:
user.greet();
Output:
Samir
thisConsider:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
const greet =
user.greet;
Now:
greet();
greet() is no longer being called through user.
Before
user.greet()
↓
this = user
After
greet()
↓
Standalone Function
↓
this is not user
Fix:
const greet =
user.greet.bind(user);
call(), apply(), and bind() cannot replace the lexical this of an arrow function.
Example:
const greet = () => {
console.log(this);
};
const user = {
name: "Samir"
};
greet.call(user);
The arrow function does not suddenly get:
this = user
Why?
Because arrow functions inherit this from their surrounding scope.
So:
Regular Function
↓
this can be controlled with
call / apply / bind
Arrow Function
↓
Lexical this
Use normal method syntax when an object method needs this.
Good:
const user = {
name: "Samir",
greet() {
console.log(this.name);
}
};
Avoid:
const user = {
name: "Samir",
greet: () => {
console.log(this.name);
}
};
when you expect this to refer to user.
Use arrow functions for callbacks when lexical this is useful.
Example:
const user = {
name: "Samir",
greet() {
setTimeout(
() => {
console.log(
this.name
);
},
1000
);
}
};
user.greet();
Output:
Samir
The arrow callback inherits this from greet().
Use bind() when passing a method as a callback and you need to preserve its this.
setTimeout(
user.greet.bind(user),
1000
);
For DOM events, event.currentTarget is often clearer than relying on this.
button.addEventListener(
"click",
event => {
console.log(
event.currentTarget
);
}
);
| Context | Value of this |
|---|---|
| Classic browser global script | window |
| ES module top level | undefined |
| Object method | Object used to call the method |
| Standalone regular function | window in non-strict classic scripts, otherwise undefined |
| Arrow function | Inherited from surrounding scope |
| DOM listener with regular function | Element the listener is attached to |
call() |
Explicitly provided value |
apply() |
Explicitly provided value |
bind() |
Bound value in returned function |
| Class method | Instance used to call the method |
| Concept | Purpose |
|---|---|
this |
Refers to the current invocation context |
| Object Method | this usually refers to the calling object |
| Regular Function | this depends on how the function is called |
| Arrow Function | Inherits surrounding this |
| Event Handler | Regular listener function gets the element as this |
call() |
Execute immediately with custom this |
apply() |
Execute immediately with custom this and array-like arguments |
bind() |
Create a new function with bound this |
| Class | this refers to the current instance when called as an instance method |
The most important rule is:
Regular Function
↓
Look at how it is called
↓
That determines this
For object methods:
user.greet()
↓
this = user
For standalone regular functions:
greet()
↓
No Calling Object
↓
undefined in strict mode
For arrow functions:
Arrow Function
↓
No Own this
↓
Uses Surrounding this
For event handlers using regular functions:
DOM Element
↓
Event Occurs
↓
Handler Runs
↓
this = Element
For explicit binding:
call()
↓
Set this + Execute Now
apply()
↓
Set this + Execute Now
Arguments as Array-like
bind()
↓
Set this
Create New Function
Execute Later
The easiest mental model is:
Normal Function
↓
How was it called?
↓
Determine this
But remember the major exception:
Arrow Function
↓
Does not create this
↓
Inherits this
from surrounding scope
Understanding this is especially useful when working with objects, classes, DOM event handlers, callbacks, timers, and older React class components.