The Secret Life of JavaScript: Understanding 'this'

Published: (December 7, 2025 at 11:41 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Chapter 2: Context Is Everything

Timothy arrived at the library with a fresh cup of tea and a confused expression. He’d spent the morning trying to debug a simple JavaScript application, and something had broken his brain.

Timothy: “Margaret, I need help. I wrote what seems like straightforward code, but this keeps pointing to the wrong thing. Sometimes it’s the object I expect, sometimes it’s undefined, sometimes it’s the global window object. It’s driving me crazy.”

Margaret looked at his code—a simple event handler attached to a button.

const user = {
  name: "Alice",
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

user.greet(); // "Hello, Alice" - works!

const greetFunction = user.greet;
greetFunction(); // "Hello, undefined" - WHAT?!

She smiled knowingly.

Margaret: “Ah, the most famous source of JavaScript confusion. You’ve discovered the truth: this isn’t what you think it is.”

Timothy: “But it’s the same function! Why does it behave differently?”

Margaret: “Because, in JavaScript, this isn’t determined by where the function is defined. It’s determined by how the function is called. That’s a completely different mental model than Python.”

Timothy groaned.

Timothy: “Already comparing to Python?”

Margaret: “You need to understand the difference. In Python, self is explicit. You write it in every method signature. It’s unambiguous. In JavaScript, this is implicit. It’s determined at call time, not definition time. And that’s where the confusion lives.”

The Four Rules of this

Margaret pulled out her worn notebook and opened to a section marked “The JavaScript Mystery.”

Rule 1: Method Call

const user = {
  name: "Alice",
  greet: function() {
    console.log(this); // The object (user)
  }
};

user.greet(); // this = user

When you call a function as a method on an object—using dot notation—this refers to the object itself. This is intuitive and what most people expect.

Rule 2: Function Call

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

greet(); // this = undefined (in strict mode) or window (in non-strict)

Calling a function directly, not as a method on an object, makes this depend on strict mode. In strict mode (modern JavaScript) this is undefined; in non‑strict mode it’s the global object (window in browsers, global in Node.js). Either way, it’s not what you usually want.

Timothy: “Why would anyone do that?”

Margaret: “They wouldn’t intentionally. But watch what happens when you extract a method from an object.”

const user = {
  name: "Alice",
  greet: function() {
    console.log("Hello, " + this.name);
  }
};

user.greet(); // "Hello, Alice" – Rule 1 (method call)

const greetFunction = user.greet; // Extract the function
greetFunction(); // "Hello, undefined" – Rule 2 (function call)

Extracting the function turns it into a plain function call, so this becomes undefined. The function doesn’t remember where it came from.

Rule 3: Constructor Call

function User(name) {
  this.name = name;
  console.log(this); // A brand‑new object
}

const alice = new User("Alice");
console.log(alice.name); // "Alice"

Using the new keyword creates a brand‑new object and sets this to that object. The constructor then populates the object with properties.

Rule 4: Explicit Binding with call, apply, and bind

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

const user = { name: "Alice" };

greet.call(user, "Hello");   // "Hello, Alice"
greet.apply(user, ["Hello"]); // "Hello, Alice"

const boundGreet = greet.bind(user);
boundGreet("Hello"); // "Hello, Alice"

call and apply explicitly set this for a single invocation, while bind returns a new function with this permanently bound.

Decision Tree for Determining this

How is the function called?

├─ With 'new'?
│  └─ Rule 3: Constructor Call
│     → this = newly created object

├─ With .call(), .apply(), or .bind()?
│  └─ Rule 4: Explicit Binding
│     → this = the object you specified

├─ As object.method()?
│  └─ Rule 1: Method Call
│     → this = the object (before the dot)

└─ Plain function()?
   └─ Rule 2: Function Call
      → this = undefined (strict) or global (non‑strict)

When debugging this problems, walk through this tree to see which rule applies.

The Problem: Losing Context

Timothy realized his original issue was Rule 2 kicking in when he extracted the method.

Margaret showed a realistic example with an event listener:

const button = document.querySelector('button');

const user = {
  name: "Alice",
  handleClick: function() {
    console.log(this.name); // What is this?
  }
};

button.addEventListener('click', user.handleClick);
// When clicked: console.log undefined
// addEventListener calls the function with `this` set to the button element,

addEventListener calls the handler with this bound to the button element, so this.name is undefined.

Fixes

Fix 1: Use an Arrow Function

const user = {
  name: "Alice",
  setupButton: function() {
    const button = document.querySelector('button');
    button.addEventListener('click', () => {
      console.log(this.name); // this = user (captured from setupButton's scope)
    });
  }
};

user.setupButton();

Arrow functions inherit this from the surrounding lexical scope, preserving the user context.

Fix 2: Use bind

button.addEventListener('click', user.handleClick.bind(user));
// When clicked: console.log "Alice"
Back to Blog

Related posts

Read more »

Functions And Arrow Functions

What are functions? If we want to put it in simple words, they are one of the main building blocks of JavaScript. They are used to organize your code into smal...