Understanding this in JavaScript: Who Is Calling the Function?

this is one of those JavaScript concepts that trips up developers at every level — beginners who cannot get it to work at all, and experienced developers who occasionally get burned by it in unexpected situations.
The reason it confuses people is that most languages have a simpler version of the concept. In Java or Python, this or self is determined by the class the method belongs to. In JavaScript, it is determined by how the function is called, not where it is defined. The same function can have a completely different this depending on who calls it.
That one idea — this is the caller — explains almost every behaviour you will ever encounter.
What this Represents
this is a keyword that refers to the context in which the current function is executing. More specifically, it refers to the object that is the subject of the current call.
Think of a function as a verb, and this as the subject of the sentence. When you say "Priya greets," Priya is doing the greeting. When you say "Rahul greets," Rahul is doing the same action. The verb is the same; the subject changes.
function greet() {
console.log(`Hello, I am ${this.name}`);
}
The function greet uses this.name. That value depends entirely on what this is when the function runs — which depends entirely on how greet is called.
JavaScript determines this at the moment of the function call, not at the moment of definition. This is the core rule. Everything else follows from it.
this in the Global Context
When you run code outside of any function or object — at the top level of a script — this refers to the global object.
In a browser, the global object is window:
console.log(this === window); // true (in browsers)
this.name = "global scope";
console.log(window.name); // "global scope"
In Node.js, the global object at the top level of a module is an empty object {}, not the true global object. This is a quirk of Node's module system.
// In Node.js (at the top of a file)
console.log(this); // {}
In practice, you rarely use this at the global level intentionally. It matters most when you accidentally end up in the global context without realising it — typically inside a regular function that is not called as a method.
this in a regular function (non-strict mode)
When a function is called on its own — not as a method of an object — this defaults to the global object in non-strict mode:
function showThis() {
console.log(this);
}
showThis(); // window (in browsers, non-strict mode)
This is one of the most common sources of bugs. You expect this to be something specific, but it ends up being window because the function was called without a clear caller.
this in strict mode
In strict mode, a standalone function call gives you undefined for this instead of the global object:
"use strict";
function showThis() {
console.log(this);
}
showThis(); // undefined
Strict mode is the safer behaviour — undefined makes the mistake obvious immediately. Non-strict mode's silent fallback to window hides the bug and can cause hard-to-trace errors when you accidentally set properties on the global object.
this Inside Objects
When a function is called as a method of an object — using dot notation — this refers to the object before the dot.
const user = {
name: "Priya",
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
user.greet(); // Hello, I am Priya
Here, user is the object before the dot. When greet is called as user.greet(), JavaScript sets this to user inside the function. So this.name is user.name, which is "Priya".
This is the most intuitive use of this. The object is clearly the caller — user.greet() reads as "user does greet," and this inside greet is user.
Multiple objects, same function
Because this is determined by the call, the same function can produce different results depending on which object calls it:
function introduce() {
console.log(`I am \({this.name}, aged \){this.age}`);
}
const person1 = { name: "Priya", age: 28, introduce };
const person2 = { name: "Rahul", age: 34, introduce };
person1.introduce(); // I am Priya, aged 28
person2.introduce(); // I am Rahul, aged 34
introduce is defined once. It is attached to two objects. When called on person1, this is person1. When called on person2, this is person2. The function is identical — only the caller changes.
Nested objects
The rule is always the immediate object before the dot:
const company = {
name: "Acme Corp",
ceo: {
name: "Ananya",
introduce() {
console.log(`I am ${this.name}`);
}
}
};
company.ceo.introduce(); // I am Ananya
The call is company.ceo.introduce(). The immediate caller is ceo, not company. So this is company.ceo, and this.name is "Ananya".
this Inside Regular Functions
The behaviour of this inside a regular function depends entirely on how the function is called — not where it is defined. This is the source of most this-related confusion.
Losing this when assigning a method to a variable
const user = {
name: "Priya",
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
user.greet(); // Hello, I am Priya — this = user
const fn = user.greet; // store the function in a variable
fn(); // Hello, I am undefined — this = window (or undefined in strict mode)
fn holds the same function as user.greet. But when you call fn(), there is no object before the dot. There is no clear caller. JavaScript falls back to the global object (or undefined in strict mode). The function lost its this the moment it was detached from user.
This happens constantly with event handlers and callbacks — you pass a method somewhere, it gets called as a plain function, and this is no longer the object you expected.
const timer = {
name: "Countdown",
start() {
setTimeout(function() {
console.log(`${this.name} started`); // this.name is undefined
}, 1000);
}
};
timer.start();
start is called on timer, so inside start, this is timer. But the callback passed to setTimeout is called by the browser's timer mechanism — not by timer. It has no caller object, so this is the global object. this.name is not "Countdown".
Preserving this with a variable
One classic solution is to capture this before entering the nested function:
const timer = {
name: "Countdown",
start() {
const self = this; // capture this = timer
setTimeout(function() {
console.log(`${self.name} started`); // Countdown started
}, 1000);
}
};
timer.start();
self is a regular variable that closes over the outer this. The inner function uses self instead of this. This works but feels like a workaround — and it is. Arrow functions solve this more cleanly.
Arrow Functions and this
Arrow functions do not have their own this. Instead, they inherit this from the surrounding lexical scope — the context where the arrow function was written, not where it is called.
const timer = {
name: "Countdown",
start() {
setTimeout(() => {
console.log(`${this.name} started`); // Countdown started
}, 1000);
}
};
timer.start();
The arrow function () => { ... } is written inside start, where this is timer. The arrow function captures that this and keeps it regardless of how or when the callback is eventually called. No self variable needed.
This makes arrow functions the natural choice for callbacks inside methods:
const user = {
name: "Priya",
hobbies: ["reading", "coding", "hiking"],
listHobbies() {
this.hobbies.forEach(hobby => {
console.log(`\({this.name} likes \){hobby}`);
});
}
};
user.listHobbies();
// Priya likes reading
// Priya likes coding
// Priya likes hiking
The arrow function passed to forEach uses this.name without any trouble, because it inherits this from listHobbies, where this is user.
When not to use arrow functions as methods
Arrow functions inherit this from where they are defined — which means if you define an arrow function directly as an object method, this will not be the object:
const user = {
name: "Priya",
greet: () => {
console.log(`Hello, I am ${this.name}`); // this is NOT user
}
};
user.greet(); // Hello, I am undefined
The arrow function is defined in the global scope (outside any function), so this in the arrow function is the global object, not user. Arrow functions are for callbacks and nested functions — regular function syntax is for object methods.
How Calling Context Changes this
The calling context is everything. Here are the three main patterns, summarised:
Pattern 1: Method call — this is the object before the dot
object.method(); // this = object
const car = {
brand: "Toyota",
describe() {
console.log(`This is a ${this.brand}`);
}
};
car.describe(); // This is a Toyota — this = car
Pattern 2: Plain function call — this is global (or undefined in strict mode)
fn(); // this = window / undefined
function describe() {
console.log(this);
}
describe(); // window (non-strict) or undefined (strict)
Pattern 3: Constructor call — this is the new object
When a function is called with new, this refers to the newly created object:
function Car(brand) {
this.brand = brand; // this = the new object
}
const myCar = new Car("Toyota");
console.log(myCar.brand); // Toyota
This is how constructor functions work — new creates a fresh object and sets it as this for the duration of the constructor call.
Explicitly Controlling this: call, apply, and bind
JavaScript gives you three tools to set this manually, regardless of how the function would normally be called.
call — invoke now with a specific this
function greet(greeting) {
console.log(`\({greeting}, I am \){this.name}`);
}
const user = { name: "Priya" };
greet.call(user, "Hello"); // Hello, I am Priya
call invokes the function immediately. The first argument becomes this; subsequent arguments are passed to the function.
apply — same as call, but arguments as an array
greet.apply(user, ["Namaste"]); // Namaste, I am Priya
apply is useful when the arguments are already in an array.
bind — create a new function with this fixed
const greetPriya = greet.bind(user);
greetPriya("Hello"); // Hello, I am Priya
greetPriya("Hi"); // Hi, I am Priya
bind does not call the function. It creates a new function with this permanently set to the object you pass. That new function always uses user as this, no matter how or where it is called later.
bind is particularly useful for event handlers:
const button = {
label: "Submit",
handleClick() {
console.log(`${this.label} was clicked`);
}
};
document.querySelector("button")
.addEventListener("click", button.handleClick.bind(button));
Without bind, the event handler would lose its this when called by the browser. With bind, this is permanently set to button.
this Inside Classes
ES6 classes use this the same way constructor functions do. Inside a class method, this refers to the instance:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I am \({this.name} and I am \){this.age} years old`);
}
}
const user1 = new User("Priya", 28);
const user2 = new User("Rahul", 34);
user1.greet(); // Hi, I am Priya and I am 28 years old
user2.greet(); // Hi, I am Rahul and I am 34 years old
The same calling-context rule applies. user1.greet() sets this to user1. user2.greet() sets this to user2.
Class methods can also lose this when passed as callbacks. The fix is the same: use bind in the constructor, or use an arrow function class field (a more modern syntax):
class User {
constructor(name) {
this.name = name;
// Option 1: bind in constructor
this.greet = this.greet.bind(this);
}
greet() {
console.log(`Hi, I am ${this.name}`);
}
}
// Option 2: arrow function as a class field (modern syntax)
class User {
constructor(name) {
this.name = name;
}
greet = () => {
console.log(`Hi, I am ${this.name}`);
};
}
Arrow function class fields capture this at construction time. They are widely used in React components for exactly this reason.
A Mental Model: The Object Before the Dot
When you are unsure what this is inside a function, ask one question: what is to the left of the dot at the point of the call?
user.greet(); // this = user
company.ceo.greet(); // this = company.ceo (the immediate caller)
greet(); // no dot — this = global / undefined
new User("Priya"); // new — this = fresh object
greet.call(user); // explicit — this = user
If there is an object before the dot, that is this. If there is no dot, this falls back to the global object or undefined. If new is involved, this is the new object being created. If call, apply, or bind is involved, this is whatever you passed as the first argument.
That covers the vast majority of real-world cases.
Common Mistakes and Their Fixes
Mistake: Passing a method as a callback and losing this
// Problem
setTimeout(user.greet, 1000); // this = window inside greet
// Fix: bind
setTimeout(user.greet.bind(user), 1000);
// Fix: arrow function wrapper
setTimeout(() => user.greet(), 1000);
Mistake: Using an arrow function as an object method
// Problem
const user = {
name: "Priya",
greet: () => console.log(this.name) // this = global, not user
};
// Fix: use regular function syntax for methods
const user = {
name: "Priya",
greet() { console.log(this.name); } // this = user
};
Mistake: Forgetting this inside a nested callback
// Problem
const user = {
name: "Priya",
loadData() {
fetchData(function(data) {
console.log(this.name); // this = window, not user
});
}
};
// Fix: arrow function
const user = {
name: "Priya",
loadData() {
fetchData((data) => {
console.log(this.name); // this = user — inherited from loadData
});
}
};
Wrapping Up
this in JavaScript is not a fixed property of a function — it is a dynamic value determined at the moment of the call. The rules, once internalised, are consistent:
Method call on an object:
thisis that object.Plain function call:
thisis global (orundefinedin strict mode).Constructor call with
new:thisis the newly created object.Explicit call with
call,apply, orbind:thisis whatever you specify.Arrow function:
thisis inherited from the surrounding lexical scope.
The mental shortcut — look at what is to the left of the dot — gets you the right answer in most cases. For the rest, understanding that this is about calling context, not definition context, fills in the gaps.
Once this clicks, a large class of JavaScript bugs becomes easy to diagnose and fix. You stop being surprised by undefined, and you start having a clear reason for every value this takes.
Comments
Join the discussionNo comments yet. Be the first to comment.
