this, call, apply & bind
this is a special keyword that refers to the object a function is working with. Unlike ordinary variables, its value isn't fixed where the function is written — it's decided by how the function is called. Understanding a handful of rules removes almost all the confusion.
Rule 1: method calls
When a function is called as object.method(), this is the object before the dot:
const user = { name: "Ana", greet() { return `Hi, I'm ${this.name}`; }, }; const robot = { name: "R2", greet: user.greet }; console.log(user.greet()); console.log(robot.greet());
Hi, I'm Ana Hi, I'm R2
It's the same function both times. What changes is the object to the left of the dot at the moment of the call.
Rule 2: plain function calls
Call a function on its own — not as a method — and this is undefined in strict mode (modules, classes, and any file with "use strict"). In old sloppy-mode scripts it's the global object instead. This is how methods "lose" their this:
"use strict"; const user = { name: "Ana", greet() { return `Hi, I'm ${this.name}`; }, }; const greetFn = user.greet; console.log(greetFn());
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
Assigning user.greet to a variable copies just the function. Called without a dot, it has no object. The same thing happens when you pass a method as a callback, like setTimeout(user.greet, 100).
Rule 3: call and apply
call and apply run a function immediately with an explicit this. They differ only in how extra arguments are passed:
function introduce(greeting, punctuation) { return `${greeting}, I'm ${this.name}${punctuation}`; } const ana = { name: "Ana" }; console.log(introduce.call(ana, "Hello", "!")); console.log(introduce.apply(ana, ["Hey", "?"]));
Hello, I'm Ana! Hey, I'm Ana?
Mnemonic: apply takes an array. With spread syntax, call covers both cases, so apply is rarely needed today.
Rule 4: bind
bind doesn't call the function — it returns a new function with this permanently fixed:
const user = { name: "Ana", greet() { return `Hi, I'm ${this.name}`; }, }; const bound = user.greet.bind(user); const callLater = fn => fn(); console.log(callLater(bound));
Hi, I'm Ana
Bound functions are the classic fix for passing methods as callbacks. bind can also pre-fill arguments: const addTen = add.bind(null, 10).
Rule 5: arrow functions inherit this
Arrow functions ignore all of the above. They don't have their own this; they use the one from the surrounding code. That makes them the easiest fix for callbacks inside methods:
const clock = { ticks: 0, run() { [1, 2, 3].forEach(() => this.ticks++); return this.ticks; }, }; console.log(clock.run());
3
A regular function () { this.ticks++ } callback would have its own this (undefined), and the code would crash.
Rule 6: constructors and classes
When a function is called with new — including class constructors — this is the brand-new object being created (lessons 38–39).
this in event handlers
In a regular-function event listener, this is the element the listener is attached to. In an arrow function, it's whatever surrounded the arrow. Using event.currentTarget instead avoids the question entirely:
Summary
| How the function is called | this is… |
|---|---|
obj.method() | obj |
fn() | undefined (strict) / global object (sloppy) |
fn.call(x), fn.apply(x) | x |
fn.bind(x) then called | x, always |
new Fn() | The new object |
| Arrow function | Whatever this was where the arrow was written |