Codeskill

Learn to code, step by step

Closures, this, and binding

Closures, this, and binding in plain terms. No mysticism. Just what the runtime actually does and the patterns that keep you out of trouble.

Closures in one sentence

A closure is a function that still sees variables from the scope where it was created, even after that outer function has finished.

function makeCounter(start = 0) {
  let count = start;
  return {
    increment() {
      count += 1;
      return count;
    },
    read() {
      return count;
    },
  };
}

const counter = makeCounter(10);
counter.increment(); // 11

The inner functions ‘close over’ count. That is how module-private state works without classes – and how event handlers remember configuration.

Closures in the wild

Closures power debounce, memoisation, and factory functions. They also cause classic loop bugs if you are not careful with var in old code – use let in loops so each iteration gets its own binding.

function debounce(fn, delayMs) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delayMs);
  };
}

What this is

this is not the function itself, and it is not the place you defined the function. It is set by how the function is called:

  • Method call: obj.method()this is obj.
  • Plain call: fn()this is undefined in strict mode (modules are strict).
  • Constructor: new Fn()this is the new object.
  • Explicit binding: fn.call(x) / apply / bind.
const button = {
  label: 'Save',
  handleClick() {
    console.log(this.label);
  },
};

button.handleClick(); // 'Save'

const loose = button.handleClick;
loose(); // undefined (strict) - lost the receiver

Arrow functions and this

Arrow functions do not have their own this. They inherit this from the surrounding lexical scope. That makes them good for callbacks inside methods when you want the outer this – and bad as object methods if you need the object as receiver.

class Panel {
  constructor(name) {
    this.name = name;
  }
  listen(el) {
    el.addEventListener('click', () => {
      console.log(this.name); // Panel instance
    });
  }
}

bind, call, and apply

bind returns a new function with this fixed. DOM APIs and timers often need it when passing methods as callbacks.

const handler = button.handleClick.bind(button);
document.addEventListener('click', handler);

Use call and apply when you want to invoke once with a chosen this and arguments. apply takes an array of args; call lists them.

Practical rules

  • Prefer regular methods on objects and classes when this should be the instance.
  • Use arrow functions for inline callbacks when you want lexical this.
  • If you see const self = this, check whether an arrow function reads clearer.
  • In event handlers, event.currentTarget is often clearer than fighting this.

Next: classes and prototypes – what you actually need from object-oriented JavaScript, without pretending it is Java.

PreviousArrays and objects properly