Skip to content
Laviyx Learn

Try

JavaScript course menu

The this Keyword in JavaScript, plus call, apply and bind

Understand this in JavaScript: how it is determined by how a function is called, methods, standalone functions in strict mode, losing this when passing methods as callbacks, arrow functions inheriting this, and fixing it with bind, call and apply.

Advanced9 min readUpdated
On this page

this is one of the most confusing parts of JavaScript, because its value is not decided where a function is written, but by how the function is called. Once you know the rules, it becomes predictable.

The rules#

How the function is calledthis is
As a method: obj.method()obj, the object before the dot
As a plain function: fn()undefined in strict mode (modules, classes)
With new: new Fn()The newly created object
With call, apply or bindWhatever you pass
An arrow functionthis from the surrounding code (arrow functions have no own this)
As a DOM event handler (regular function)The element the listener is attached to

Methods#

JavaScript
const user = {
  name: "Asha",
  greet() {
    return `Hi, I am ${this.name}`;
  },
};

console.log(user.greet());

const other = { name: "Ravi", greet: user.greet };
console.log(other.greet());
Output
Hi, I am Asha
Hi, I am Ravi

The same function gives different results: this is whatever object it was called on.

Losing this#

Taking a method out of its object, or passing it as a callback, loses this:

JavaScript
const user = {
  name: "Asha",
  greet() {
    return `Hi, I am ${this?.name}`;
  },
};

const greet = user.greet;     // just the function, no object
console.log(greet());
Output
Hi, I am undefined

Called as a plain function, this is undefined in strict mode. (The example uses this?.name to avoid an error; with this.name it would throw a TypeError.) This bites most often with callbacks:

JavaScript
class Timer {
  seconds = 0;
  tick() {
    this.seconds++;
    return this.seconds;
  }
}

const timer = new Timer();
const runLater = (callback) => callback();   // like setTimeout or addEventListener

try {
  runLater(timer.tick);
} catch (error) {
  console.log(error.constructor.name, "-", error.message);
}
Output
TypeError - Cannot read properties of undefined (reading 'seconds')

Fix 1: arrow functions#

Arrow functions take this from where they are defined. Wrapping the call in an arrow function keeps the method attached to its object:

JavaScript
class Timer {
  seconds = 0;
  tick() {
    this.seconds++;
  }
}
const timer = new Timer();

const run = (fn) => { fn(); fn(); fn(); };
run(() => timer.tick());
console.log(timer.seconds);
Output
3

A class field holding an arrow function is another common pattern, especially in UI code: handleClick = () => { this.count++; };. Each instance gets its own bound function.

Fix 2: bind#

fn.bind(obj) returns a new function whose this is permanently set to obj:

JavaScript
const user = {
  name: "Meera",
  greet() {
    return `Hi, I am ${this.name}`;
  },
};

const boundGreet = user.greet.bind(user);
console.log(boundGreet());
console.log([1].map(boundGreet)[0]);
Output
Hi, I am Meera
Hi, I am Meera

call and apply#

call and apply call a function immediately with a chosen this. They differ only in how arguments are passed:

JavaScript
function introduce(greeting, punctuation) {
  return `${greeting}, I am ${this.name}${punctuation}`;
}

const asha = { name: "Asha" };

console.log(introduce.call(asha, "Hello", "!"));
console.log(introduce.apply(asha, ["Namaste", "."]));
Output
Hello, I am Asha!
Namaste, I am Asha.
MethodRuns now?Arguments
fn.call(thisArg, a, b)YesOne by one
fn.apply(thisArg, [a, b])YesAs an array
fn.bind(thisArg, a, b)No, returns a new functionOptionally pre-filled

Today, spread syntax often replaces apply (Math.max(...numbers)), and arrow functions often replace bind.

Arrow functions inside methods#

Because arrow functions inherit this, they are perfect for callbacks inside methods:

JavaScript
const team = {
  name: "Laviyx",
  members: ["Asha", "Ravi"],
  list() {
    return this.members.map((m) => `${m} works at ${this.name}`);
  },
};

console.log(team.list());
Output
[ 'Asha works at Laviyx', 'Ravi works at Laviyx' ]

A regular function (m) { ... } callback here would have its own this (undefined), and this.name would fail.

this in event handlers#

In a DOM event listener written as a regular function, this is the element; with an arrow function, it is not. Using event.currentTarget is clearer and works in both:

JavaScript
button.addEventListener("click", function () {
  this.disabled = true;               // this === button
});
button.addEventListener("click", (event) => {
  event.currentTarget.disabled = true;
});

Practice#

Practice exercise

This counter breaks when increment is passed to run. Fix it in two different ways: once with bind, and once with an arrow function.

JavaScript
const counter = {
  count: 0,
  increment() { this.count++; },
};
function run(fn) { fn(); fn(); }
run(counter.increment);
Show solution
JavaScript
const counter = {
  count: 0,
  increment() {
    this.count++;
  },
};
function run(fn) {
  fn();
  fn();
}

run(counter.increment.bind(counter));
console.log(counter.count);

run(() => counter.increment());
console.log(counter.count);
Output
2
4

Key takeaway#

Finished this lesson?