Skip to main content

Command Palette

Search for a command to run...

The Magic of this , call(), apply() , and bind() in JavaScript

Published
7 min read
The Magic of  this , call(),  apply() , and bind() in JavaScript

If you've ever written JavaScript and seen this behave in a way that made no sense, you're not alone. this, along with call(), apply(), and bind(), is one of those topics that trips up beginners (and sometimes experienced devs). This post breaks it down from the ground up—with real examples and the mistakes we all make—so you can use them confidently.

What is this?

In simple terms, this is a reference to the "owner" of the code that is currently running.

It’s not always "the function itself" or "the file." It changes depending on how and where the function is called. That’s why it feels unpredictable at first.

Rule of thumb

- Who called this function? → Often, that’s what this refers to.

- In which context is the code running? → That context often defines this.

We’ll make this precise with the rules below.

How JavaScript Decides What this Is

JavaScript follows a few clear rules. Once you know them, this becomes predictable.

1. Default binding (standalone function call)

When you call a function by itself (not as a method on an object), this usually refers to the global object:

- In a browser: window

- In Node.js: global (or globalThis in modern JS)

function sayHello() {
  console.log(this);
}

sayHello(); // In browser: Window {...}  |  In Node: global object

Common beginner mistake: Assuming this is always the function or the script. In strict mode 'use strict'), this in such calls is undefined, which can cause errors if you don’t expect it

2. Implicit binding (method call)

When you call a function as a method on an object (e.g. obj.method()), this is that object.

const user = {
  name: 'Priya',
  greet() {
    console.log(`Hello, ${this.name}!`);
  }
};

user.greet(); // "Hello, Priya!"  →  this = user

So: who called it?user. So *this = user**.

Common beginner mistake: Storing the method in a variable and then calling it. That "loses" the object, so this is no longer user:

onst greet = user.greet;
greet(); // "Hello, undefined!" (or error in strict mode)
// this is no longer user—it's window/global or undefined

3. Explicit binding call, apply, bind)

When you explicitly tell JavaScript: "when this function runs, set this to this value," you’re using explicit binding. That’s exactly what call(), apply(), and bind() do.

4. new binding (constructor)

When you call a function with new, this is the newly created object:

function Person(name) {
  this.name = name;
}

const p = new Person('Rahul');
console.log(p.name); // "Rahul"  →  this was the new object

Part 2: call() – Call a function with a chosen this

Syntax:

function.call(thisArg, arg1, arg2, ...)

- First argument: what this should be inside the function.

- Rest of the arguments: passed to the function as normal parameters.

Example:

function introduce(greeting, punctuation) {
  console.log(`\({greeting}, I'm \){this.name}${punctuation}`);
}

const person = { name: 'Sneha' };

introduce.call(person, 'Hi', '!');
// "Hi, I'm Sneha!"
// this = person, greeting = 'Hi', punctuation = '!'

When to use call():

- When you want to run a function once with a specific this and known arguments.

- When borrowing a method from another object.

- When you need to pass arguments one by one.

Part 3: apply() – Same idea, arguments as an array

Syntax:

function.apply(thisArg, [arg1, arg2, ...])

- First argument: what this should be (same as call).

- Second argument: one array of all the arguments for the function.

Example:

introduce.apply(person, ['Hello', '.']);
// "Hello, I'm Sneha."

So:

- call(this, a, b, c) → arguments listed one by one.

- apply(this, [a, b, c]) → arguments in a single array.

When to use apply():

- When your arguments are already in an array (e.g. from another function or API).

- Historically used for things like Math.max.apply(null, array). In modern JS you’d often use spread: Math.max(...array).

If you have an array and want to use call, you can do:

fn.call(thisArg, ...array)` — that’s like `apply(thisArg, array)

Part 4: bind() – Get a new function with this (and optional args) fixed

Syntax:

function.bind(thisArg, arg1?, arg2?, ...)

- First argument: what this will always be in the new function.

- Optional: you can also fix the first few arguments (partial application).

- Returns: a new function. It does not run the original function immediately.

Example:

const introduceSneha = introduce.bind(person);
introduceSneha('Hey', '!!');
// "Hey, I'm Sneha!!"
// this is always person, no matter how you call introduceSneha

Fixing both this and some arguments:

const sayHiSneha = introduce.bind(person, 'Hi');
sayHiSneha('!');  // "Hi, I'm Sneha!"

When to use bind():

- When you need to pass a callback that must keep a specific this (e.g. event handlers, setTimeout, React class methods).

- When you want a function that always runs with a fixed this (and optionally fixed arguments).

Common beginner mistake: Calling bind() and expecting the original function to run. It doesn’t—it only returns a new function. You still have to call that returned function.

introduce.bind(person, 'Hi', '!');   // Does nothing visible—returns a function
introduce.bind(person, 'Hi', '!')(); // Correct: call the returned function

Quick comparison

| Method    | When it runs     | Arguments form   | Returns        |
|----------|------------------|------------------|----------------|
| `call()` | Immediately      | List: (a, b, c)  | Function result |
| `apply()`| Immediately      | Array: [a, b, c] | Function result |
| `bind()` | Not immediately  | List (optional)  | New function   |

Part 5: Fixing real-world mistakes

Mistake 1: Losing this in a callback

const counter = {
  count: 0,
  increment() {
    this.count++;
    console.log(this.count);
  }
};

setTimeout(counter.increment, 1000); // NaN or error — this is not counter!

Fix with bind:

setTimeout(counter.increment.bind(counter), 1000); // 1 (after 1 second)

Mistake 2: Passing a method as a callback (e.g. in event listeners)

button.addEventListener('click', obj.handleClick); // this might be the button, not obj

If you need this inside handleClick to be obj:

button.addEventListener('click', obj.handleClick.bind(obj));

Mistake 3: Assuming this in nested functions

const obj = {
  name: 'Dev',
  outer() {
    function inner() {
      console.log(this.name); // this is not obj!
    }
    inner();
  }
};
obj.outer(); // undefined or error

Fix: Capture this in a variable, or use an arrow function for inner (arrow functions don’t have their own this; they use the surrounding one):

outer() {
  const self = this;
  function inner() {
    console.log(self.name);
  }
  inner();
}
// Or: inner = () => console.log(this.name);

Part 6: How this fits with arrow functions

Arrow functions (**do not** have their own this). They use the this of the scope where they were defined (lexical this).

const obj = {
  name: 'Arrow',
  regular: function() { console.log(this.name); },
  arrow: () => console.log(this.name)
};

obj.regular(); // "Arrow"
obj.arrow();   // undefined (or window.name) — arrow took this from outer scope

So:

- Normal functions: this = who called them / what you pass via callapplybind.

- Arrow functions: this = from the surrounding code; callapplybind don’t change their this.

Use arrow functions when you want to keep the surrounding this (e.g. in callbacks). Use regular functions when you need this to be the object or something you pass with call apply bind.

Summary

- this = the "owner" of the current execution, decided by how (and where) the function is called.

- call(thisArg, ...args) = run function now with that this and those arguments.

- apply(thisArg, [args]) = same, but arguments in one array.

- bind(thisArg, ...args?) = return a new function with that this (and optional fixed arguments); call it later.

Once you know these rules and the typical mistakes (callbacks, nested functions, passing methods), you can debug this issues quickly and use call, apply, and bind on purpose instead of by trial and error.

If this helped you, consider sharing it with someone who’s still confused by this. Happy coding!

Happy learning !