DEV Community

Samuel Ochaba
Samuel Ochaba

Posted on

Understanding Function Binding in JavaScript: A Beginner's Guide

If you've ever passed a method as a callback in JavaScript and wondered why this suddenly became undefined, you're not alone. Let's break down this confusing topic together.

The Problem: When this Goes Missing

Imagine you're building a simple greeting app:

let person = {
  name: "Sarah",
  greet() {
    console.log(`Hi, I'm ${this.name}!`);
  }
};

person.greet(); // Works fine: "Hi, I'm Sarah!"
Enter fullscreen mode Exit fullscreen mode

So far, so good! But watch what happens when we try to use this with setTimeout:

setTimeout(person.greet, 1000); // "Hi, I'm undefined!"
Enter fullscreen mode Exit fullscreen mode

Wait, what?

Here's what happened: when you pass person.greet to setTimeout, you're basically doing this:

let greetFunction = person.greet;
setTimeout(greetFunction, 1000);
Enter fullscreen mode Exit fullscreen mode

The function got separated from its object, and now it doesn't know who this is anymore. It's like giving someone directions to your house but forgetting to tell them which city you live in!

Solution #1: Wrap It Up

The easiest fix? Wrap the method call in an arrow function:

let person = {
  name: "Sarah",
  greet() {
    console.log(`Hi, I'm ${this.name}!`);
  }
};

setTimeout(() => person.greet(), 1000); // "Hi, I'm Sarah!"
Enter fullscreen mode Exit fullscreen mode

This works because the arrow function "remembers" the person object from its surrounding code.

But There's a Catch!

What if the person variable changes before the timeout runs?

let person = {
  name: "Sarah",
  greet() {
    console.log(`Hi, I'm ${this.name}!`);
  }
};

setTimeout(() => person.greet(), 2000);

// Oops, person gets reassigned!
person = {
  name: "Mike",
  greet() {
    console.log(`Hi, I'm ${this.name}!`);
  }
};

// After 2 seconds: "Hi, I'm Mike!" (not Sarah!)
Enter fullscreen mode Exit fullscreen mode

This might not be what you wanted! 🤔

Solution #2: The Magic of bind()

This is where bind() comes to the rescue. It creates a new function with this permanently locked in:

let person = {
  name: "Sarah",
  greet() {
    console.log(`Hi, I'm ${this.name}!`);
  }
};

let boundGreet = person.greet.bind(person);

setTimeout(boundGreet, 2000); // "Hi, I'm Sarah!"

// Even if we change person...
person = { name: "Mike" };

// After 2 seconds: Still "Hi, I'm Sarah!"
Enter fullscreen mode Exit fullscreen mode

Think of bind() as creating a copy of the function that remembers its this value forever, no matter what happens to the original object.

Real-World Example: Event Listeners

Here's a practical scenario with a button click handler:

class Counter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count++;
    console.log(`Count: ${this.count}`);
  }
}

let counter = new Counter();
let button = document.querySelector('#myButton');

// Won't work - loses 'this'
button.addEventListener('click', counter.increment);

// Works perfectly!
button.addEventListener('click', counter.increment.bind(counter));
Enter fullscreen mode Exit fullscreen mode

Bonus: Pre-filling Arguments with bind()

Here's a cool trick: bind() can also lock in arguments!

function announce(greeting, name) {
  console.log(`${greeting}, ${name}!`);
}

// Create a specialized version
let sayHello = announce.bind(null, "Hello");

sayHello("Emma");    // "Hello, Emma!"
sayHello("Oliver");  // "Hello, Oliver!"
Enter fullscreen mode Exit fullscreen mode

We used null because we don't need this here—we're just fixing the first argument to always be "Hello".

Let's try another example with a shopping cart:

function calculatePrice(taxRate, price) {
  return price + (price * taxRate);
}

// Create a function with pre-set tax rate
let calculateWithTax = calculatePrice.bind(null, 0.08);

console.log(calculateWithTax(100)); // 108 (100 + 8% tax)
console.log(calculateWithTax(50));  // 54 (50 + 8% tax)
Enter fullscreen mode Exit fullscreen mode

This is called partial application—we're creating a more specific version of a general function.

Quick Reference

Use a wrapper function when:

  • You need the most current value of the object
  • The code is simple and straightforward

Use bind() when:

  • You want to lock in the current this value
  • You're passing methods to event listeners or timers
  • You want to create specialized versions of functions

Wrapping Up

The this keyword can be tricky, but now you know:

  • Why this gets lost when passing methods around
  • How to use arrow functions as a quick fix
  • How bind() creates a permanent connection to this
  • How to use bind() for partial application

Next time you see undefined where you expected a value, remember to check if this has gone missing!

Happy coding!


Have questions or other examples to share? Drop them in the comments below!

Top comments (0)