Learn JavaScript’s ‘this’ keyword and context binding. Master the 5 binding rules, call/apply/bind methods, and arrow functions.
Why does this sometimes point to the wrong object? Why does your method work perfectly when called directly, but break when passed as a callback? And how do call, apply, and bind let you take control?
The this keyword is one of JavaScript’s most confusing features, but it follows specific rules. According to the Stack Overflow Developer Survey, this binding is consistently cited as one of the trickiest parts of JavaScript for developers to master. Once you understand them, you’ll never be confused again.
What you’ll learn in this guide:
What this actually is and why it’s determined at call time
The 5 binding rules that determine this (in priority order)
How call(), apply(), and bind() work and when to use each
Arrow functions and why they handle this differently
Common pitfalls and how to avoid them
Prerequisite: This guide builds on Scope & Closures. Understanding scope will help you see why this behaves differently than regular variables.
Think about the word “I” in everyday conversation. It’s a simple word, but its meaning changes completely depending on who is speaking:
Copy
Ask AI
┌─────────────────────────────────────────────────────────────────┐│ THE PRONOUN "I" │├─────────────────────────────────────────────────────────────────┤│ ││ Alice says: "I am a developer" ││ ↓ ││ "I" = Alice ││ ││ Bob says: "I am a designer" ││ ↓ ││ "I" = Bob ││ ││ The SAME word "I" refers to DIFFERENT people ││ depending on WHO is speaking! ││ │└─────────────────────────────────────────────────────────────────┘
This is exactly how this works in JavaScript! The keyword this is like the pronoun “I”. It refers to different objects depending on who is “speaking” (which object is calling the function).
Copy
Ask AI
const alice = { name: "Alice", introduce() { return "I am " + this.name; // "I" = this = alice }};const bob = { name: "Bob", introduce() { return "I am " + this.name; // "I" = this = bob }};alice.introduce(); // "I am Alice"bob.introduce(); // "I am Bob"
But here’s where JavaScript gets interesting. What if Alice could make Bob say her words? Like a ventriloquist making a puppet speak?
Copy
Ask AI
// Alice borrows Bob's voice to introduce herselfbob.introduce.call(alice); // "I am Alice" (Bob's function, Alice's this)
That’s what call, apply, and bind do. They let you control who “I” refers to, regardless of which function is speaking.
The this keyword is a special identifier that JavaScript automatically creates in every function execution context. It refers to the object that is currently executing the code, typically the object that “owns” the method being called. Unlike most languages where this is fixed at definition time, JavaScript determines this dynamically at call time, based on how a function is invoked.
Here’s what makes JavaScript different from many other languages:
this is determined when the function is CALLED, not when it’s defined.
This is called dynamic binding, and it’s both powerful and confusing. The same function can have different this values depending on how you call it:
Copy
Ask AI
function showThis() { return this;}const obj = { showThis };// Same function, different this values:showThis(); // undefined (strict mode) or globalThisobj.showThis(); // obj (the object before the dot)showThis.call({}); // {} (explicitly specified)
In non-strict mode, plain function calls return globalThis (the global object: window in browsers, global in Node.js).
When JavaScript needs to figure out what this refers to, it follows these rules in order of priority. Higher priority rules override lower ones.
Copy
Ask AI
BINDING RULES (Highest to Lowest Priority)┌─────────────────────────────────────────┐│ 1. new Binding (Highest) │├─────────────────────────────────────────┤│ 2. Explicit Binding (call/apply/ ││ bind) │├─────────────────────────────────────────┤│ 3. Implicit Binding (method call) │├─────────────────────────────────────────┤│ 4. Default Binding (plain call) │├─────────────────────────────────────────┤│ 5. Arrow Functions (lexical) ││ (Special case - no own this) │└─────────────────────────────────────────┘
Arrow functions are listed last not because they’re lowest priority, but because they work differently. As defined in the ECMAScript 2015 specification, arrow functions do not have their own this binding at all — they inherit it from the enclosing lexical scope. We’ll cover them in detail.
When a function is called with the new keyword, this is set to a brand new object that’s automatically created.
Copy
Ask AI
class Person { constructor(name) { // 'this' is the new object being created this.name = name; this.greet = function() { return `Hi, I'm ${this.name}`; }; }}const alice = new Person("Alice");console.log(alice.name); // "Alice"console.log(alice.greet()); // "Hi, I'm Alice"
You can explicitly specify what this should be using call(), apply(), or bind(). This overrides implicit and default binding.
Copy
Ask AI
function introduce() { return `I'm ${this.name}, a ${this.role}`;}const alice = { name: "Alice", role: "developer" };const bob = { name: "Bob", role: "designer" };// Explicitly set 'this' to aliceintroduce.call(alice); // "I'm Alice, a developer"// Explicitly set 'this' to bobintroduce.call(bob); // "I'm Bob, a designer"
We’ll cover call, apply, and bind in detail in the next section. For now, just know that explicit binding has higher priority than implicit binding:
Copy
Ask AI
const alice = { name: "Alice", greet() { return `Hi, I'm ${this.name}`; }};const bob = { name: "Bob" };// Even though we're calling alice.greet(), we can override 'this'alice.greet.call(bob); // "Hi, I'm Bob" (explicit wins!)
When a function is called without any of the above conditions, default binding applies.In strict mode (which you should always use): this is undefined.In non-strict mode: this is the global object (window in browsers, global in Node.js).
// Without strict mode (not recommended)function showThis() { return this;}showThis(); // window (in browser) or global (in Node.js)
Always use strict mode! Non-strict mode’s default binding to globalThis is dangerous. It can accidentally create or modify global variables, leading to hard-to-find bugs.ES6 modules and classes are automatically in strict mode.
Arrow functions are special. They don’t have their own this binding at all. Instead, they inherit this from their enclosing scope at the time they’re defined.
Copy
Ask AI
const user = { name: "Alice", // Regular function: 'this' is determined by how it's called regularGreet: function() { return `Hi, I'm ${this.name}`; }, // Arrow function: 'this' is inherited from where it's defined arrowGreet: () => { return `Hi, I'm ${this.name}`; }};user.regularGreet(); // "Hi, I'm Alice" (this = user)user.arrowGreet(); // "Hi, I'm undefined" (this = enclosing scope, not user!)
Wait, why is arrowGreet showing undefined? Because the arrow function was defined in the object literal, and the enclosing scope at that point is the module/global scope, not the user object.
You cannot change an arrow function’s this using call, apply, or bind:
Copy
Ask AI
const arrowFn = () => this;const obj = { name: "Object" };// These all return the same thing - the lexical 'this'arrowFn(); // lexical thisarrowFn.call(obj); // lexical this (call is ignored!)arrowFn.apply(obj); // lexical this (apply is ignored!)arrowFn.bind(obj)(); // lexical this (bind is ignored!)
A common modern pattern is using arrow functions as class methods:
Copy
Ask AI
class Button { constructor(label) { this.label = label; } // Arrow function as class field - 'this' is always the instance handleClick = () => { console.log(`Button "${this.label}" clicked`); }}const btn = new Button("Submit");// Works even when extracted!const handler = btn.handleClick;handler(); // "Button "Submit" clicked" ✓// Works in event listeners!document.querySelector('button').addEventListener('click', btn.handleClick);
This pattern is widely used in React class components and other UI frameworks to ensure event handlers always have the correct this.
class Animal { constructor(name) { this.name = name; } speak() { return `${this.name} makes a sound`; }}class Dog extends Animal { speak() { // Call parent method with 'this' context const parentSays = Animal.prototype.speak.call(this); return `${parentSays}. ${this.name} barks!`; }}const dog = new Dog("Rex");dog.speak(); // "Rex makes a sound. Rex barks!"
The apply() method is almost identical to call(), but arguments are passed as an array (or array-like object).Syntax:
Copy
Ask AI
func.apply(thisArg, [arg1, arg2, ...])
Basic example:
Copy
Ask AI
function greet(greeting, punctuation) { return `${greeting}, I'm ${this.name}${punctuation}`;}const alice = { name: "Alice" };// Same result as call(), but args in an arraygreet.apply(alice, ["Hello", "!"]); // "Hello, I'm Alice!"
apply() shines when your arguments are already in array form:
Copy
Ask AI
function introduce(greeting, role, company) { return `${greeting}! I'm ${this.name}, ${role} at ${company}.`;}const alice = { name: "Alice" };const args = ["Hello", "engineer", "TechCorp"];// When args are already an array, apply is naturalintroduce.apply(alice, args); // "Hello! I'm Alice, engineer at TechCorp."// With call, you'd need to spreadintroduce.call(alice, ...args); // Same result
The bind() method is different from call() and apply(). It doesn’t call the function immediately. Instead, it returns a new function with this permanently bound.Syntax:
// Arguments object (old-school, but still seen in legacy code)function sum() { // 'arguments' is array-like but not an array (see MDN: Arguments object) return Array.prototype.reduce.call( arguments, (total, n) => total + n, 0 );}sum(1, 2, 3, 4); // 10// NodeList from DOM (browser-only example)const divs = document.querySelectorAll('div'); // NodeList, not Arrayconst texts = Array.prototype.map.call(divs, div => div.textContent);// Modern alternative: Array.from()const textsModern = Array.from(divs).map(div => div.textContent);// Or spreadconst textsSpread = [...divs].map(div => div.textContent);
const user = { name: "Alice", greet: () => { return `Hi, I'm ${this.name}`; // 'this' is NOT user! }};user.greet(); // "Hi, I'm undefined"
Why it happens: Arrow functions don’t have their own this. The this here is from the surrounding scope (module/global), not user.Solution: Use regular functions for object methods:
Copy
Ask AI
const user = { name: "Alice", greet() { // Shorthand method syntax return `Hi, I'm ${this.name}`; // ✓ this = user }};user.greet(); // "Hi, I'm Alice"
// 1. Cannot be used with 'new'const ArrowClass = () => {};new ArrowClass(); // TypeError: ArrowClass is not a constructor// 2. No own 'arguments' object// Arrow functions inherit 'arguments' from enclosing function scope (if any)function outer() { const arrow = () => { console.log(arguments); // Works! Uses outer's arguments }; arrow();}outer(1, 2, 3); // logs [1, 2, 3]// But at module/global scope with no enclosing function:const arrow = () => { console.log(arguments); // ReferenceError: arguments is not defined};// Use rest parameters instead (recommended)const arrowWithRest = (...args) => { console.log(args); // Works everywhere!};// 3. No own 'super' binding (inherits from enclosing class method if any)// 4. Cannot be used as generators// There's no arrow generator syntax - you must use function*function* generatorFn() { yield 1; } // Works// () =>* { yield 1; } // No such syntax exists
Answer:"Hi, I'm undefined"When greet is assigned to a variable and called without an object, implicit binding is lost. Default binding applies, and in strict mode this is undefined.
Question 2: What does this log?
Copy
Ask AI
class Counter { count = 0; increment = () => { this.count++; }}const counter = new Counter();const inc = counter.increment;inc();inc();console.log(counter.count);
Answer:2Arrow function class fields have lexical this bound to the instance. Even when extracted, this still refers to counter.
Question 3: What does this log?
Copy
Ask AI
function greet() { return `Hello, ${this.name}!`;}const alice = { name: "Alice" };const bob = { name: "Bob" };const greetAlice = greet.bind(alice);console.log(greetAlice.call(bob));
Answer:"Hello, Alice!"Once a function is bound with bind(), its this cannot be changed — not even with call(). The binding is permanent.
Answer:10 (and likely a TypeError in strict mode)The callback function inside forEach has its own this (default binding), which is undefined in strict mode. The fix is to use an arrow function: numbers.forEach((n) => { this.value += n; }).
Question 6: What does this log?
Copy
Ask AI
function multiply(a, b) { return a * b;}const double = multiply.bind(null, 2);console.log(double(5));console.log(double.length);
Answer:10 and 1bind creates a partially applied function. double(5) returns 2 * 5 = 10. The length property of a bound function reflects remaining parameters: multiply has 2 params, we pre-filled 1, so double.length is 1.
The this keyword refers to the object that is currently executing the function. Unlike most languages where this is determined at definition time, JavaScript determines this at call time based on how the function is invoked. As documented on MDN, this follows specific binding rules: default, implicit, explicit (call/apply/bind), and new binding.
What is the difference between call, apply, and bind?
call invokes the function immediately with a specified this and individual arguments. apply also invokes immediately but takes arguments as an array. bind does not invoke the function — it returns a new function with this permanently set. Use call/apply for one-time invocations and bind when you need a reusable function with a fixed context.
Why do arrow functions not have their own 'this'?
Arrow functions were designed to solve the common problem of losing this context in callbacks. According to the ECMAScript 2015 specification, arrow functions do not have their own this binding — they inherit this from their enclosing lexical scope. This makes them ideal for callbacks and event handlers where you want to preserve the outer this.
How do you fix 'this' losing context in a callback?
There are three common solutions: use an arrow function (which inherits this lexically), use .bind(this) to create a bound function, or store this in a variable like const self = this. Arrow functions are the most modern and concise approach and are recommended for most callback scenarios.
What are the five binding rules for 'this' in order of priority?
From highest to lowest priority: (1) new binding — this is the newly created object. (2) Explicit binding — call, apply, or bind set this. (3) Implicit binding — the object before the dot becomes this. (4) Default binding — this is globalThis (or undefined in strict mode). (5) Arrow functions — this is inherited from the enclosing scope and cannot be overridden.