Few things in JavaScript cause as much confusion as the keyword this. People coming from other languages expect it to mean “the current object,” the way it does in Java or C#. In JavaScript it’s looser than that — and far more surprising. The same function can have this pointing to one thing on Monday and a completely different thing on Tuesday, depending on nothing more than how you called it.
Here’s the good news up front: once the rule clicks, this stops being magic. The whole topic comes down to a single idea — this is decided at call time, by how the function is invoked, not by where the function was written. Get that into your bones and the rest is detail. Let’s build it up carefully.
You’ll be more comfortable here if you’ve already met functions and objects, since this lives at the intersection of the two.
What is this?
this is a special keyword that exists automatically inside every function. It refers to some object — but which object isn’t fixed. JavaScript fills in this fresh each time the function runs, based on the way the call was made.
That’s the part that trips everyone up. In most languages, a method belongs to its class and this always points to the instance. In JavaScript a function is just a value; it doesn’t “belong” to anything permanently. So the question is never “what does this mean inside this function?” The real question is always: “how was this function called?” Answer that, and you know this.
function show() {
console.log(this);
}
That same show function could print a window object, an undefined, or a user object — entirely depending on the call site. Below we’ll go through every way a function can be called, and what this becomes in each case.
The four call patterns
There are four common ways to invoke a function, and each one sets this differently. Learn these four and you’ve covered nearly everything you’ll meet in real code.
1. Method call: this is the object before the dot
When you call a function as a property of an object — object.method() — this becomes the object that’s sitting to the left of the dot. This is the most intuitive case and the one you’ll use most.
const user = {
name: "John Doe",
greet() {
console.log("Hi, I'm " + this.name);
},
};
user.greet(); // "Hi, I'm John Doe"
Inside greet, this is user, because the call was user.greet() — user is the thing before the dot. So this.name reads user.name. Simple and predictable.
The key insight: it’s the call that matters, not where the function was defined. Watch what happens if we pull the method off the object:
const greetAlone = user.greet;
greetAlone(); // 💥 this.name is now undefined (or an error)
Same function, but now there’s no object before the dot, so this is no longer user. The function didn’t change — the way we called it did. This single example is the heart of the whole topic.
2. Plain function call: this is undefined (or the global object)
When you call a function on its own — no object, no dot — this depends on whether you’re in strict mode:
function standalone() {
console.log(this);
}
standalone();
// strict mode (modules, classes): undefined
// non-strict (old scripts): the global object (window in a browser)
In modern JavaScript — ES modules, class bodies, anything with "use strict" — a plain function call gives this === undefined. That’s actually helpful, because it turns silent bugs into loud errors instead of quietly writing to the global object.
The lost this problem
This is the single most common this bug. You pull a method out of its object and call it later — through a variable, a callback, or a setTimeout — and suddenly this is gone:
const user = { name: "John Doe", greet() { return this.name; } };
setTimeout(user.greet, 1000); // this is NOT user here!setTimeout doesn’t call it as user.greet() — it stores the function and later calls it plain, with no object before the dot. So this is no longer user. The fixes (arrow functions, bind) come up below — but first, recognize the pattern: any time a method gets passed around and called elsewhere, its this is at risk.
3. Constructor call: this is the brand-new object
When you call a function with the new keyword, JavaScript creates a fresh empty object and points this at it. The function fills in that object, and (unless you return something else) new hands it back automatically.
function User(name) {
this.name = name;
this.role = "member";
}
const u = new User("Jane Doe");
console.log(u.name); // "Jane Doe"
console.log(u.role); // "member"
Here this is the new object being built. The same thing happens inside a class constructor — classes are mostly nicer syntax over exactly this mechanism. If you’ve used new Date() or new Map(), you’ve already relied on this pattern without thinking about it.
4. Explicit call: you choose this with call, apply, bind
Sometimes you want to decide this yourself, regardless of how the function would normally be called. JavaScript gives you three methods on every function for exactly that: call, apply, and bind.
function greet(greeting) {
console.log(greeting + ", " + this.name);
}
const person = { name: "John Doe" };
greet.call(person, "Hello"); // "Hello, John Doe"
greet.apply(person, ["Hi"]); // "Hi, John Doe"
const bound = greet.bind(person);
bound("Hey"); // "Hey, John Doe"
call and apply invoke the function right now with a this you specify — the only difference is call takes arguments one by one, while apply takes them as an array. bind is different: it doesn’t call the function, it returns a new function with this permanently locked to whatever you passed. That returned function will keep that this no matter how it’s later called — which is exactly what makes bind the classic fix for the “lost this” problem.
Remembering call vs apply
call and apply do the same job; only how you pass arguments differs. A tiny memory hook: apply takes an array. call spreads them out: fn.call(thisArg, a, b, c). apply bundles them: fn.apply(thisArg, [a, b, c]). With the spread operator (...) you rarely need apply anymore — fn.call(thisArg, ...args) covers it.
Arrow functions: this is inherited, not reset
Arrow functions break the rules above — on purpose, and helpfully. An arrow function has no this of its own. It doesn’t care how it was called. Instead, it borrows this from the surrounding code where it was written — its lexical scope. This is the one place where “where it’s written” actually matters.
That sounds abstract until you see the problem it solves. This is one of the most common real bugs:
const timer = {
seconds: 0,
start() {
setInterval(function () {
this.seconds++; // 💥 this is NOT timer here
console.log(this.seconds); // NaN, over and over
}, 1000);
},
};
The callback inside setInterval is a plain function call, so its this is undefined (or the global object), not timer. The increment silently fails. Now swap in an arrow function:
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++; // ✅ this is timer
console.log(this.seconds); // 1, 2, 3, ...
}, 1000);
},
};
The arrow function has no this of its own, so this inside it is whatever this was in start — and start was called as timer.start(), so its this is timer. The arrow simply inherits it. That’s why arrows are the go-to choice for callbacks inside methods.
Don't use an arrow function as an object method
Because arrows inherit this instead of getting their own, they’re the wrong choice for a top-level method on an object:
const user = {
name: "John Doe",
greet: () => {
console.log(this.name); // undefined — NOT user
},
};
user.greet();Here the arrow inherits this from the surrounding scope (the module or global), not from user — so this.name is wrong. The rule of thumb: use a regular function (or method shorthand) for object methods so this can bind to the object, and use an arrow function for callbacks inside those methods so they inherit the right this.
A complete mental model
Put it all together and you can read this in any code by asking one question at the call site — how was this function called? Walk down the list in order:
// 1. new Fn() → this is the brand-new object
// 2. obj.fn() → this is obj (the thing before the dot)
// 3. fn.call(x) etc. → this is x (whatever you passed)
// 4. fn() → this is undefined (strict) / global (sloppy)
// 5. arrow function → ignore all of the above; this comes from the
// surrounding scope where the arrow was written
Notice that arrow functions sit outside the list — they opt out of the whole mechanism and borrow from their surroundings. For every other function, the answer is at the call site, never the definition. When this surprises you, don’t re-read the function body. Go find where it was called and run through these cases. The answer is always there.
When to use what
A few practical habits that keep this out of your way:
- Object methods → regular functions or method shorthand (
greet() { ... }). They need their ownthisso it can bind to the object. - Callbacks inside a method → arrow functions. They inherit the method’s
this, which is almost always what you want. - Passing a method as a callback → bind it (
this.handle = this.handle.bind(this)) or wrap it in an arrow (() => this.handle()), so itsthissurvives the trip. - Modern classes → either define handlers as arrow-function class fields, or bind in the constructor. Both keep
thispointing at the instance.
If you’ve worked with scope and closures, notice the contrast: ordinary variables are resolved lexically (by where the code sits), but this for regular functions is resolved dynamically (by how the call happens). Arrow functions are the bridge — they make this behave lexically, like a normal variable. That single difference explains most of the confusion people have with this.
Wrapping up
The keyword this only feels chaotic until you internalize the one rule:
thisis set by how a function is called, not where it’s defined. When in doubt, look at the call site.- Method call (
obj.fn()) →thisis the object before the dot. - Plain call (
fn()) →thisisundefinedin strict mode, the global object otherwise. - Constructor call (
new Fn()) →thisis the freshly created object. - Explicit call (
call,apply,bind) →thisis whatever you pass;bindlocks it permanently into a new function. - Arrow functions have no
thisof their own — they inherit it from the surrounding scope. Perfect for callbacks inside methods; wrong for object methods themselves.
Master those cases and the most “magical” word in JavaScript becomes one of the most predictable. Next time this is undefined, you’ll know exactly where to look — and you’ll usually fix it with an arrow function or a bind in seconds.