The Secret Life of JavaScript: Understanding 'this'
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
thiskeeps pointing to the wrong thing. Sometimes it’s the object I expect, sometimes it’sundefined, sometimes it’s the globalwindowobject. 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:
thisisn’t what you think it is.”
Timothy: “But it’s the same function! Why does it behave differently?”
Margaret: “Because, in JavaScript,
thisisn’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,
selfis explicit. You write it in every method signature. It’s unambiguous. In JavaScript,thisis 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"