What you'll be able to do

By the end of this page you should be able to use call, apply, and bind to control this, borrow a method from one object for another, and pass a method into a callback without losing the object you meant.

Previous post: this in plain English.

Who this is for

  • People who saw this become undefined after setTimeout(obj.method, 0)
  • Beginners who copy .bind(this) from React class examples and want the plain-JS reason
  • Anyone building event handlers that need to stay tied to a specific object

You can skip this if you already reach for bind confidently when a method is passed around. Come back when a timer or DOM handler breaks this.

Three tools, one job: choose this on purpose

Every function in JavaScript can be invoked with an explicit this value. call and apply call the function now with a chosen this. bind returns a new function that remembers this for later.

Scenario A — you have a method and a different object. user.greet.call(otherUser) runs greet with this = otherUser.

Scenario B — you pass a method to setTimeout. setTimeout(user.greet.bind(user), 1000) keeps this = user when the timer fires.

The rule: if the function runs immediately and you know the arguments, call or apply is enough; if something else will call the function later, bind is the usual fix.

call — invoke now with this and arguments

function greet(greeting) {
  console.log(greeting + ", " + this.name);
}

const user = { name: "Asha" };
const admin = { name: "Dev" };

greet.call(user, "Hi");    // Hi, Asha
greet.call(admin, "Hello"); // Hello, Dev

Scenario A — borrow a method. Arrays have slice. NodeLists do not. Array.prototype.slice.call(nodeList) turns a NodeList into a real array using call.

Scenario B — logging with a chosen context. A utility calls logger.log.call(app, message) so this inside log is the app object, not window.

apply — same as call, arguments in an array

function sum(a, b, c) {
  return a + b + c;
}
const nums = [2, 3, 5];
console.log(sum.apply(null, nums)); // 10

Scenario A — you already have an array of arguments. apply spreads them without rest syntax (older style, still readable in legacy code).

Scenario B — Math.max on an array. Math.max.apply(null, scores) was the classic pattern before Math.max(...scores).

Modern code often uses fn.call(thisArg, ...args) or spread instead of apply, but interviews and older libraries still mention apply.

bind — a new function with this locked in

const user = {
  name: "Asha",
  sayHi: function () {
    console.log("Hi, " + this.name);
  },
};

const bound = user.sayHi.bind(user);
setTimeout(bound, 0); // Hi, Asha — not lost

button.addEventListener("click", user.sayHi.bind(user));

Scenario A — event listener. The browser calls your handler; bind(user) ensures this.name still works.

Scenario B — partial application. fetch.bind(null, "/api/items") can pre-fill the first argument (less common for beginners, but bind is how it works).

Mistake to avoid: bind returns a new function. user.sayHi.bind(user) does not change user.sayHi itself.

A tiny practice file

<!DOCTYPE html>
<html>
  <body>
    <button id="go">Test call / bind</button>
    <pre id="out"></pre>
    <script>
      const out = document.getElementById("out");
      const user = {
        name: "Asha",
        greet: function (word) {
          out.textContent += word + ", " + this.name + "\n";
        },
      };
      const stranger = { name: "Guest" };

      document.getElementById("go").addEventListener("click", function () {
        out.textContent = "";
        user.greet.call(stranger, "Hello"); // Hello, Guest
        user.greet.call(user, "Hi");        // Hi, Asha
        setTimeout(user.greet.bind(user, "Later"), 50);
      });
    </script>
  </body>
</html>

Click the button — you should see call switch this, then bind survive setTimeout.

Mistakes I see a lot

1. Calling bind but not using the returned function. user.sayHi.bind(user) alone does nothing until you pass that result to setTimeout or addEventListener.

2. Binding inside a loop without understanding a new function each time. Usually fine; sometimes people expect bind to mutate the original.

3. Using call when you needed bind. setTimeout(user.greet.call(user), 0) runs immediately and passes undefined to setTimeout.

4. Forgetting Array.prototype.slice.call is about this, not magic syntax. You are borrowing slice and telling it to treat the NodeList as this.

What to try before the next post

  1. Pass obj.method to setTimeout without and with .bind(obj).
  2. Use Array.prototype.slice.call on document.querySelectorAll("p") and log .map.
  3. Build the tiny HTML file.

Next in this series: constructor functions — objects from functions and why new matters.

Try this next outside the series

These mental models become more useful when you can point at a real framework problem they explain.