Advanced

this and Closures

Understand this binding, closures, and lexical scope.

What you will see

2

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

See how this is chosen by the way a function is called, lose it when you pass a method aside, and recover private state with a closure.

1

Call a method that reads this.name. Inside a method, this is the object the method was called on, so the greeting resolves the right name.

const user = { name: "Ada", greet() { return `Hi, ${this.name}`; } }; console.log(user.greet());

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Hi, Ada
In a method, this is the object the method is called on. Assigning the method to a variable first loses this binding.
2

Copy a method to a plain variable and call it on its own. Without an object to call it on, this is undefined and the lookup returns undefined instead of a name.

const user = { name: "Ada", greet() { return this.name; } }; const fn = user.greet; console.log(fn());

Before you run it, say out loud what it will print. Then open the answer.

What it prints
undefined
Passing a method around drops its this. Recover it with .bind(user), an arrow function, or by calling user.greet() directly.
3

Build a counter whose inner functions keep a private count alive after the factory returns. Call it twice, then read the value that cannot be reached from the outside.

function makeCounter() { let count = 0; return { increment: () => ++count, getCount: () => count }; } const c = makeCounter(); c.increment(); c.increment(); console.log(c.getCount());

Before you run it, say out loud what it will print. Then open the answer.

What it prints
2
A closure captures outer variables that stay alive for the inner functions. Those variables cannot be read or written from outside the closure.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

this is bound by the call pattern; closures capture outer variables that persist after the outer function returns.
1 Function called -> this determined by pattern
↓
2 Method call -> this is the object
↓
3 Arrow function -> this is lexical from definition
↓
4 Closure keeps outer scope alive for later use
Why this works
The four ways this binds

this is determined by how a function is invoked:

// 1. Default: standalone call
fn();                     // undefined (strict) / global

// 2. Method call
obj.method();             // obj

// 3. new
new Fn();                 // the new instance

// 4. explicit: bind/call/apply
fn.call(obj);             // obj

Arrow functions ignore all of these and always use the lexical this from their definition site. Prefer arrow functions for callbacks to avoid accidental binding loss.

Scope chains

Variables resolve outward through nested scopes:

const global = "G";
function outer() {
  const mid = "M";
  function inner() {
    console.log(global, mid);   // both visible
  }
  inner();
}

Each scope can see variables from its parent scopes. Closures extend this: a returned inner function keeps its parent scope alive. This is the foundation of module patterns and state management.

Examples

this in methods

this follows the call site

const user = {
  name: "Ada",
  greet() {
    return `Hi, ${this.name}`;   // 'this' is the caller
  }
};
console.log(user.greet());   // Hi, Ada

In a method, this is the object the method is called on. Passing the method around loses this:

const fn = user.greet;
fn();   // 'this' is undefined in strict mode

Use .bind(user) or an arrow function to preserve it.

Arrow functions and this

Lexical this

class Timer {
  constructor() { this.count = 0; }
  start() {
    setInterval(() => {
      this.count++;   // 'this' is the Timer instance
    }, 1000);
  }
}

Arrow functions do not have their own this; they capture it from the surrounding code. This is why they are ideal for callbacks inside methods.

Closures capturing state

Private state

function makeCounter() {
  let count = 0;      // closed over
  return {
    increment: () => ++count,
    getCount: () => count
  };
}
const c = makeCounter();
c.increment();
c.increment();
c.getCount();   // 2 (count is not accessible outside)

A closure gives functions private state. The captured variables cannot be reached from outside, which enforces encapsulation.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Asynchronous JavaScript
Previous Lesson
Asynchronous JavaScript
Next Lesson
Destructuring and Spread
Destructuring and Spread →