Skip to main content
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?
const user = {
  name: "Alice",
  greet() {
    return `Hi, I'm ${this.name}`;
  }
};

user.greet();           // "Hi, I'm Alice" - works!
const greet = user.greet;
greet();                // "Hi, I'm undefined" - broken!
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.

The Pronoun “I”: A Real-World Analogy

Think about the word “I” in everyday conversation. It’s a simple word, but its meaning changes completely depending on who is speaking:
┌─────────────────────────────────────────────────────────────────┐
│                      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).
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?
// Alice borrows Bob's voice to introduce herself
bob.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.

What is this in JavaScript?

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.

The Key Insight: Call-Time Binding

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:
function showThis() {
  return this;
}

const obj = { showThis };

// Same function, different this values:
showThis();        // undefined (strict mode) or globalThis
obj.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).

Why Does JavaScript Work This Way?

This design allows for incredible flexibility:
  1. Method sharing: Multiple objects can share the same function
  2. Dynamic behavior: Functions can work with any object that has the right properties
  3. Borrowing methods: You can use methods from one object on another
// One function, many objects
function greet() {
  return `Hello, I'm ${this.name}!`;
}

const alice = { name: "Alice", greet };
const bob = { name: "Bob", greet };
const charlie = { name: "Charlie", greet };

alice.greet();    // "Hello, I'm Alice!"
bob.greet();      // "Hello, I'm Bob!"
charlie.greet();  // "Hello, I'm Charlie!"
The trade-off? You need to understand the rules that determine this. Let’s dive in.

The 5 Binding Rules (Priority Order)

When JavaScript needs to figure out what this refers to, it follows these rules in order of priority. Higher priority rules override lower ones.
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.

Rule 1: new Binding (Highest Priority)

When a function is called with the new keyword, this is set to a brand new object that’s automatically created.
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"

What new Does Under the Hood

When you call new Person("Alice"), JavaScript performs these 4 steps:
1

Create an empty object

A brand new empty object is created: {}
2

Link the prototype

The new object’s internal [[Prototype]] is set to the constructor’s prototype property.
// Conceptually:
newObject.__proto__ = Person.prototype;
3

Bind this and execute

The constructor function is called with this bound to the new object. This is where your constructor code runs.
// Conceptually:
Person.call(newObject, "Alice");
4

Return the object

If the constructor doesn’t explicitly return an object, the new object is returned automatically.
// Conceptually:
return newObject;
Here’s a simplified implementation of what new does:
// What 'new' does behind the scenes
function simulateNew(Constructor, ...args) {
  // Step 1: Create empty object
  const newObject = {};
  
  // Step 2: Link prototype if it's an object
  // (If prototype isn't an object, newObject keeps Object.prototype)
  if (Constructor.prototype !== null && typeof Constructor.prototype === 'object') {
    Object.setPrototypeOf(newObject, Constructor.prototype);
  }
  
  // Step 3: Bind this and execute
  const result = Constructor.apply(newObject, args);
  
  // Step 4: Return object (unless constructor returns a non-primitive)
  return result !== null && typeof result === 'object' ? result : newObject;
}

// These are equivalent:
const alice1 = new Person("Alice");
const alice2 = simulateNew(Person, "Alice");

ES6 Classes: The Modern Syntax

With ES6 classes, the syntax is cleaner but the behavior is identical:
class Rectangle {
  constructor(width, height) {
    this.width = width;   // 'this' = new Rectangle instance
    this.height = height;
  }
  
  getArea() {
    return this.width * this.height;  // 'this' = the instance
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.getArea());  // 50
For more on constructors, the new keyword, and prototypes, see the Object Creation & Prototypes concept page.

Rule 2: Explicit Binding (call, apply, bind)

You can explicitly specify what this should be using call(), apply(), or bind(). This overrides implicit and default binding.
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 alice
introduce.call(alice);   // "I'm Alice, a developer"

// Explicitly set 'this' to bob
introduce.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:
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!)

Rule 3: Implicit Binding (Method Call)

When a function is called as a method of an object (using dot notation), this is set to the object before the dot.
const user = {
  name: "Alice",
  greet() {
    return `Hello, I'm ${this.name}`;
  }
};

// The object before the dot becomes 'this'
user.greet();  // "Hello, I'm Alice" (this = user)

The “Left of the Dot” Rule

A simple way to remember: look left of the dot when the function is called.
const company = {
  name: "TechCorp",
  department: {
    name: "Engineering",
    getName() {
      return this.name;
    }
  }
};

// What's left of the dot at call time?
company.department.getName();  // "Engineering" (this = department)
Common trap: It’s the object immediately before the dot that matters, not the outermost object. In the example above, this is department, not company.

The Implicit Binding Gotcha: Lost Context

This trips up many developers. When you extract a method from an object, it loses its implicit binding:
const user = {
  name: "Alice",
  greet() {
    return `Hello, I'm ${this.name}`;
  }
};

// This works
user.greet();  // "Hello, I'm Alice"

// But extracting the method loses 'this'!
const greetFn = user.greet;
greetFn();     // "Hello, I'm undefined" (strict mode: this = undefined)
Why? Because greetFn() is a plain function call. There’s no dot, so implicit binding doesn’t apply. We fall through to default binding.
IMPLICIT BINDING LOST
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   user.greet()                                              │
│        ↑                                                    │
│        └── Object before dot → this = user ✓               │
│                                                             │
│   const greetFn = user.greet;                               │
│   greetFn()                                                 │
│       ↑                                                     │
│       └── No dot! → Default binding → this = undefined ✗   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
This happens constantly with:
  • Callbacks: setTimeout(user.greet, 1000)
  • Event handlers: button.addEventListener('click', user.greet)
  • Array methods: [1,2,3].forEach(user.process)
We’ll cover solutions in the “Gotchas” section.

Rule 4: Default Binding (Plain Function Call)

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).
"use strict";

function showThis() {
  return this;
}

showThis();  // undefined (strict mode)
// 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.

When Default Binding Applies

Default binding kicks in when:
  1. Plain function call: myFunction()
  2. IIFE: (function() { ... })()
  3. Callback without binding: setTimeout(function() { ... }, 100)
"use strict";

// All of these use default binding (this = undefined)
function regularFunction() {
  return this;
}

regularFunction();  // undefined

(function() {
  return this;  // undefined
})();

setTimeout(function() {
  console.log(this);  // undefined
}, 100);

Rule 5: Arrow Functions (Lexical this)

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.
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.

Where Arrow Functions Shine

Arrow functions are perfect for callbacks where you want to preserve the outer this:
class Counter {
  constructor() {
    this.count = 0;
  }
  
  // Problem: regular function loses 'this' in callback
  startBroken() {
    setInterval(function() {
      this.count++;  // ERROR: 'this' is undefined!
      console.log(this.count);
    }, 1000);
  }
  
  // Solution: arrow function preserves 'this'
  startFixed() {
    setInterval(() => {
      this.count++;  // Works! 'this' is the Counter instance
      console.log(this.count);
    }, 1000);
  }
}

Arrow Functions Cannot Be Rebound

You cannot change an arrow function’s this using call, apply, or bind:
const arrowFn = () => this;

const obj = { name: "Object" };

// These all return the same thing - the lexical 'this'
arrowFn();            // lexical this
arrowFn.call(obj);    // lexical this (call is ignored!)
arrowFn.apply(obj);   // lexical this (apply is ignored!)
arrowFn.bind(obj)();  // lexical this (bind is ignored!)

Arrow Functions as Class Fields

A common modern pattern is using arrow functions as class methods:
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.

The Decision Flowchart

When you need to figure out what this is, follow this flowchart:
                    ┌─────────────────────────┐
                    │  Is it an arrow         │
                    │  function?              │
                    └───────────┬─────────────┘
                           │    │
                    YES ◄──┘    └──► NO
                     │               │
                     ▼               ▼
        ┌─────────────────┐    ┌─────────────────────┐
        │ this = enclosing│    │ Was it called with  │
        │ scope's this    │    │ 'new'?              │
        │ (DONE)          │    └──────────┬──────────┘
        └─────────────────┘           │    │
                                YES ◄─┘    └──► NO
                                 │               │
                                 ▼               ▼
                    ┌─────────────────┐    ┌─────────────────────┐
                    │ this = new      │    │ Was call/apply/bind │
                    │ object          │    │ used?               │
                    │ (DONE)          │    └──────────┬──────────┘
                    └─────────────────┘           │    │
                                           YES ◄──┘    └──► NO
                                            │               │
                                            ▼               ▼
                               ┌─────────────────┐    ┌─────────────────────┐
                               │ this = specified│    │ Was it called as    │
                               │ object          │    │ obj.method()?       │
                               │ (DONE)          │    └──────────┬──────────┘
                               └─────────────────┘           │    │
                                                      YES ◄──┘    └──► NO
                                                       │               │
                                                       ▼               ▼
                                          ┌─────────────────┐    ┌─────────────────┐
                                          │ this = obj      │    │ Default binding:│
                                          │ (left of dot)   │    │ this = undefined│
                                          │ (DONE)          │    │ (strict mode)   │
                                          └─────────────────┘    └─────────────────┘

How Do call(), apply(), and bind() Work?

These three methods give you explicit control over this. They’re built into every function in JavaScript.

Quick Comparison

MethodInvokes Function?ArgumentsReturns
call()Yes, immediatelyIndividual: call(this, a, b, c)Function result
apply()Yes, immediatelyArray: apply(this, [a, b, c])Function result
bind()NoIndividual: bind(this, a, b)New function
Memory trick:
  • Call = Commas (arguments separated by commas)
  • Apply = Array (arguments in an array)
  • Bind = Back later (returns a function for later use)

call() — Call with This

The call() method calls a function with a specified this value and arguments provided individually. Syntax:
func.call(thisArg, arg1, arg2, ...)
Basic example:
function greet(greeting, punctuation) {
  return `${greeting}, I'm ${this.name}${punctuation}`;
}

const alice = { name: "Alice" };
const bob = { name: "Bob" };

greet.call(alice, "Hello", "!");   // "Hello, I'm Alice!"
greet.call(bob, "Hi", "...");      // "Hi, I'm Bob..."

Use Case: Method Borrowing

call() is perfect for borrowing methods from one object to use on another:
const arrayLike = {
  0: "a",
  1: "b", 
  2: "c",
  length: 3
};

// arrayLike doesn't have array methods, but we can borrow them!
const result = Array.prototype.slice.call(arrayLike);
console.log(result);  // ["a", "b", "c"]

const joined = Array.prototype.join.call(arrayLike, "-");
console.log(joined);  // "a-b-c"

Use Case: Calling Parent Methods

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!"

apply() — Apply with Array

The apply() method is almost identical to call(), but arguments are passed as an array (or array-like object). Syntax:
func.apply(thisArg, [arg1, arg2, ...])
Basic example:
function greet(greeting, punctuation) {
  return `${greeting}, I'm ${this.name}${punctuation}`;
}

const alice = { name: "Alice" };

// Same result as call(), but args in an array
greet.apply(alice, ["Hello", "!"]);  // "Hello, I'm Alice!"

Classic Use Case: Finding Max/Min

Before ES6, apply() was the way to use Math.max() with an array:
const numbers = [5, 2, 9, 1, 7];

// Old way with apply
const max = Math.max.apply(null, numbers);  // 9
const min = Math.min.apply(null, numbers);  // 1
Modern alternative: Use the spread operator instead!
const numbers = [5, 2, 9, 1, 7];
const max = Math.max(...numbers);  // 9
const min = Math.min(...numbers);  // 1
The spread syntax is cleaner and more readable. Use apply() mainly when you need to set this AND spread arguments.

When Arguments Are Already an Array

apply() shines when your arguments are already in array form:
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 natural
introduce.apply(alice, args);  // "Hello! I'm Alice, engineer at TechCorp."

// With call, you'd need to spread
introduce.call(alice, ...args);  // Same result

bind() — Bind for Later

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:
const boundFunc = func.bind(thisArg, arg1, arg2, ...)
Basic example:
function greet() {
  return `Hello, I'm ${this.name}`;
}

const alice = { name: "Alice" };

// bind() returns a NEW function
const greetAlice = greet.bind(alice);

// Call it whenever you want
greetAlice();  // "Hello, I'm Alice"
greetAlice();  // "Hello, I'm Alice" (still works!)

Key Characteristic: Permanent Binding

Once bound, the this value cannot be changed, not even with call() or apply():
function showThis() {
  return this.name;
}

const alice = { name: "Alice" };
const bob = { name: "Bob" };

const boundToAlice = showThis.bind(alice);

boundToAlice();            // "Alice"
boundToAlice.call(bob);    // "Alice" (call ignored!)
boundToAlice.apply(bob);   // "Alice" (apply ignored!)
boundToAlice.bind(bob)();  // "Alice" (bind ignored!)

Use Case: Event Handlers

This is a common use of bind():
class Toggle {
  constructor() {
    this.isOn = false;
    
    // Without bind, 'this' would be the button element
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.isOn = !this.isOn;
    console.log(`Toggle is ${this.isOn ? 'ON' : 'OFF'}`);
  }
  
  attachTo(button) {
    button.addEventListener('click', this.handleClick);
  }
}

Use Case: setTimeout and setInterval

class Countdown {
  constructor(start) {
    this.count = start;
  }
  
  start() {
    // Without bind, 'this' would be undefined in the callback
    setInterval(this.tick.bind(this), 1000);
  }
  
  tick() {
    console.log(this.count--);
  }
}

const countdown = new Countdown(10);
countdown.start();  // 10, 9, 8, 7...

Use Case: Partial Application

bind() can also pre-fill arguments, creating a specialized version of a function:
function multiply(a, b) {
  return a * b;
}

// Create specialized functions
const double = multiply.bind(null, 2);    // 'a' is always 2
const triple = multiply.bind(null, 3);    // 'a' is always 3

double(5);  // 10 (2 * 5)
triple(5);  // 15 (3 * 5)
double(7);  // 14 (2 * 7)
This technique is called partial application. You’re partially applying arguments to create a more specific function.
function greet(greeting, name) {
  return `${greeting}, ${name}!`;
}

// Partial application: pre-fill the greeting
const sayHello = greet.bind(null, "Hello");
const sayGoodbye = greet.bind(null, "Goodbye");

sayHello("Alice");    // "Hello, Alice!"
sayHello("Bob");      // "Hello, Bob!"
sayGoodbye("Alice");  // "Goodbye, Alice!"

Common Patterns & Use Cases

Pattern 1: Method Borrowing

Use array methods on array-like objects:
// 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 Array
const texts = Array.prototype.map.call(divs, div => div.textContent);

// Modern alternative: Array.from()
const textsModern = Array.from(divs).map(div => div.textContent);
// Or spread
const textsSpread = [...divs].map(div => div.textContent);

Pattern 2: Preserving Context in Classes

The three main approaches to ensure this is correct in class methods:
class Player {
  constructor(name) {
    this.name = name;
    this.score = 0;
    
    // Approach 1: Bind in constructor
    this.incrementBound = this.incrementBound.bind(this);
  }
  
  // Regular method - needs binding when used as callback
  incrementBound() {
    this.score++;
    return this.score;
  }
  
  // Approach 2: Arrow function class field
  incrementArrow = () => {
    this.score++;
    return this.score;
  }
  
  // Approach 3: Bind at call site (inline)
  regularIncrement() {
    this.score++;
    return this.score;
  }
}

const player = new Player("Alice");

// All these work correctly:
setTimeout(player.incrementBound, 100);     // Approach 1
setTimeout(player.incrementArrow, 100);     // Approach 2
setTimeout(player.regularIncrement.bind(player), 100);  // Approach 3
setTimeout(() => player.regularIncrement(), 100);       // Approach 3 alt
Which approach is best?
  • Arrow class fields (Approach 2) are the cleanest for most cases
  • Bind in constructor (Approach 1) is useful when you need the method to also work as a regular method
  • Inline bind/arrow (Approach 3) is fine for one-off uses but creates new functions each render in React

Pattern 3: Partial Application for Reusable Functions

// Generic logging function
function log(level, timestamp, message) {
  console.log(`[${level}] ${timestamp}: ${message}`);
}

// Create specialized loggers
const logError = log.bind(null, "ERROR");
const logWarning = log.bind(null, "WARNING");
const logInfo = log.bind(null, "INFO");

const now = new Date().toISOString();

logError(now, "Database connection failed");
// [ERROR] 2024-01-15T10:30:00.000Z: Database connection failed

logInfo(now, "Server started");
// [INFO] 2024-01-15T10:30:00.000Z: Server started

The Gotchas: Where this Goes Wrong

The problem:
class Timer {
  constructor() {
    this.seconds = 0;
  }
  
  start() {
    setInterval(function() {
      this.seconds++;  // ERROR: this is undefined!
      console.log(this.seconds);
    }, 1000);
  }
}
Why it happens: The callback function uses default binding, so this is undefined in strict mode.Solutions:
// Solution 1: Arrow function
start() {
  setInterval(() => {
    this.seconds++;  // ✓ Arrow inherits 'this'
  }, 1000);
}

// Solution 2: bind()
start() {
  setInterval(function() {
    this.seconds++;  // ✓ Bound to Timer instance
  }.bind(this), 1000);
}

// Solution 3: Store reference (old-school)
start() {
  const self = this;
  setInterval(function() {
    self.seconds++;  // ✓ Using closure
  }, 1000);
}
The problem:
const user = {
  name: "Alice",
  greet() {
    return `Hi, I'm ${this.name}`;
  }
};

const greet = user.greet;
greet();  // "Hi, I'm undefined"
Why it happens: Assigning the method to a variable loses the implicit binding.Solutions:
// Solution 1: Keep as method call
user.greet();  // ✓ "Hi, I'm Alice"

// Solution 2: Bind when extracting
const greet = user.greet.bind(user);
greet();  // ✓ "Hi, I'm Alice"

// Solution 3: Wrapper function
const greet = () => user.greet();
greet();  // ✓ "Hi, I'm Alice"
The problem:
const calculator = {
  value: 0,
  
  add(numbers) {
    numbers.forEach(function(n) {
      this.value += n;  // ERROR: this is undefined!
    });
    return this.value;
  }
};
Why it happens: The inner function has its own this (default binding), it doesn’t inherit from add().Solutions:
// Solution 1: Arrow function (recommended)
add(numbers) {
  numbers.forEach((n) => {
    this.value += n;  // ✓ Arrow inherits 'this'
  });
  return this.value;
}

// Solution 2: Use thisArg parameter
add(numbers) {
  numbers.forEach(function(n) {
    this.value += n;  // ✓ 'this' passed as second arg
  }, this);
  return this.value;
}

// Solution 3: bind()
add(numbers) {
  numbers.forEach(function(n) {
    this.value += n;  // ✓ Bound to calculator
  }.bind(this));
  return this.value;
}
The problem:
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:
const user = {
  name: "Alice",
  greet() {  // Shorthand method syntax
    return `Hi, I'm ${this.name}`;  // ✓ this = user
  }
};

user.greet();  // "Hi, I'm Alice"

Arrow Functions: The Modern Solution

Arrow functions were introduced in ES6 partly to solve this confusion. They work fundamentally differently.

How Arrow Functions Handle this

  1. No own this: Arrow functions don’t create their own this binding
  2. Lexical inheritance: They use this from the enclosing scope
  3. Permanent: Cannot be changed by call, apply, or bind
const obj = {
  name: "Object",
  
  regularMethod: function() {
    console.log("Regular:", this.name);  // "Object"
    
    // Nested regular function - loses 'this'
    function inner() {
      console.log("Inner regular:", this);  // undefined
    }
    inner();
    
    // Nested arrow function - keeps 'this'
    const innerArrow = () => {
      console.log("Inner arrow:", this.name);  // "Object"
    };
    innerArrow();
  }
};

When to Use Arrow Functions vs Regular Functions

Use CaseArrow FunctionRegular Function
Object methods❌ No✅ Yes
Class methods (in prototype)❌ No✅ Yes
Callbacks needing outer this✅ Yes❌ No (needs bind)
Event handlers in classes✅ Yes (as class fields)⚠️ Needs binding
Functions needing own this❌ No✅ Yes
Constructor functions❌ No (can’t use new)✅ Yes
Methods using arguments❌ No (no arguments)✅ Yes

Arrow Functions as Class Fields

This is the most common pattern in modern JavaScript:
class SearchBox {
  constructor(element) {
    this.element = element;
    this.query = "";
    
    // Attach event listener - arrow function ensures correct 'this'
    this.element.addEventListener('input', this.handleInput);
  }
  
  // Arrow function as class field
  handleInput = (event) => {
    this.query = event.target.value;  // 'this' is always SearchBox instance
    this.performSearch();
  }
  
  performSearch = () => {
    console.log(`Searching for: ${this.query}`);
  }
}

Limitations of Arrow Functions

// 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

Key Takeaways

The key things to remember about this, call, apply, and bind:
  1. this is determined at call time — Not when the function is defined, but when it’s called. This is called dynamic binding.
  2. 5 binding rules in priority order — new binding > explicit binding > implicit binding > default binding (arrow functions are special).
  3. “Left of the dot” rule — In method calls like obj.method(), this is the object immediately left of the dot.
  4. Extracting methods loses thisconst fn = obj.method; fn() loses implicit binding. This is the #1 source of this bugs.
  5. call() and apply() invoke immediately — They set this and call the function right away. call takes comma-separated args, apply takes an array.
  6. bind() returns a new function — It permanently binds this for later use. The binding cannot be overridden, even with call or apply.
  7. Arrow functions have no own this — They inherit this from their enclosing scope (lexical binding). Perfect for callbacks.
  8. Arrow functions can’t be reboundcall, apply, and bind have no effect on arrow functions’ this.
  9. Use arrow class fields for event handlershandleClick = () => {} ensures this is always the instance, even when extracted.
  10. Strict mode changes default binding — In strict mode, plain function calls have this as undefined, not the global object.

Test Your Knowledge

Try to figure out what this refers to in each example before revealing the answer.
const user = {
  name: "Alice",
  greet() {
    return `Hi, I'm ${this.name}`;
  }
};

const greet = user.greet;
console.log(greet());
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.
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.
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.
const obj = {
  name: "Outer",
  inner: {
    name: "Inner",
    getName() {
      return this.name;
    }
  }
};

console.log(obj.inner.getName());
Answer: "Inner"With implicit binding, this is the object immediately to the left of the dot at call time. That’s obj.inner, not obj.
const calculator = {
  value: 10,
  add(numbers) {
    numbers.forEach(function(n) {
      this.value += n;
    });
    return this.value;
  }
};

console.log(calculator.add([1, 2, 3]));
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; }).
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.

Frequently Asked Questions

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.
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.
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.
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.
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.


Reference

Articles

Videos

Last modified on February 17, 2026