The 'this' Keyword in JavaScript Objects Guide

Understand how the this keyword works in JavaScript objects. Covers implicit binding, explicit binding with call/apply/bind, arrow function behavior, common pitfalls, and practical patterns for preserving this context.

JavaScriptbeginner
14 min read

The this keyword is one of JavaScript's most confusing features for beginners. Unlike other languages where this always points to the class instance, JavaScript's this changes depending on how a function is called, not where it is defined. This dynamic behavior makes this both powerful and error-prone, especially when working with object methods, callbacks, and event handlers.

This guide covers every binding rule, shows you how this behaves in different contexts, and teaches you reliable patterns for controlling what this points to.

What is this?

this is a special keyword that refers to the current execution context. In the context of an object method, this typically refers to the object that owns the method:

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

The Four Binding Rules

JavaScript determines the value of this using four rules, checked in this priority order:

PriorityRuleExamplethis Value
1 (Highest)new bindingnew User()New instance
2Explicit bindingfn.call(obj)obj
3Implicit bindingobj.method()obj
4 (Lowest)Default bindingfn()undefined (strict) / window (sloppy)

Rule 1: Implicit Binding (Method Call)

When you call a function as a method of an object, this refers to the object on the left side of the dot:

javascriptjavascript
const calculator = {
  value: 0,
  add(n) {
    this.value += n;
    return this;
  },
  subtract(n) {
    this.value -= n;
    return this;
  },
  getResult() {
    return this.value;
  }
};
 
calculator.add(10).subtract(3);
console.log(calculator.getResult()); // 7
// `this` refers to `calculator` in each method call

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

You can explicitly set this using call(), apply(), or bind():

javascriptjavascript
function introduce() {
  return `I'm ${this.name}, age ${this.age}`;
}
 
const alice = { name: "Alice", age: 28 };
const bob = { name: "Bob", age: 32 };
 
// call: invoke immediately with `this` set
console.log(introduce.call(alice)); // "I'm Alice, age 28"
console.log(introduce.call(bob));   // "I'm Bob, age 32"

call vs apply vs bind

javascriptjavascript
function formatUser(greeting, punctuation) {
  return `${greeting}, I'm ${this.name}${punctuation}`;
}
 
const user = { name: "Alice" };
 
// call: arguments passed individually
console.log(formatUser.call(user, "Hello", "!"));
// "Hello, I'm Alice!"
 
// apply: arguments passed as array
console.log(formatUser.apply(user, ["Hey", "."]));
// "Hey, I'm Alice."
 
// bind: returns a NEW function with `this` permanently set
const greetAlice = formatUser.bind(user, "Hi");
console.log(greetAlice("?")); // "Hi, I'm Alice?"
console.log(greetAlice("!")); // "Hi, I'm Alice!"
MethodInvokes Immediately?Arguments FormatReturns
call(thisArg, a, b)YesIndividualReturn value
apply(thisArg, [a, b])YesArrayReturn value
bind(thisArg, a)NoIndividual (partial)New bound function

Rule 3: new Binding

When a function is called with new, this refers to the newly created instance:

javascriptjavascript
function Product(name, price) {
  // `this` is the new empty object
  this.name = name;
  this.price = price;
  this.inStock = true;
}
 
const laptop = new Product("Laptop", 999);
console.log(laptop.name);    // "Laptop"
console.log(laptop.inStock); // true

Rule 4: Default Binding

When a function is called without any object context, this falls back to the default:

javascriptjavascript
"use strict";
 
function showThis() {
  console.log(this);
}
 
showThis(); // undefined (strict mode)
// In non-strict mode, this would be the global object (window in browser)

Arrow Functions and this

Arrow functions do not have their own this. They inherit this from the enclosing lexical scope at the time they are defined:

javascriptjavascript
const team = {
  name: "Engineering",
  members: ["Alice", "Bob", "Charlie"],
 
  // Regular method: `this` = team
  showMembers() {
    // Arrow function: inherits `this` from showMembers
    this.members.forEach(member => {
      console.log(`${member} is on the ${this.name} team`);
    });
  }
};
 
team.showMembers();
// "Alice is on the Engineering team"
// "Bob is on the Engineering team"
// "Charlie is on the Engineering team"

Why Arrow Functions Fail as Methods

javascriptjavascript
const user = {
  name: "Alice",
 
  // WRONG: arrow function as method
  greet: () => {
    return `Hi, I'm ${this.name}`;
    // `this` is inherited from the outer scope, NOT from `user`
  },
 
  // CORRECT: regular method
  greetCorrect() {
    return `Hi, I'm ${this.name}`;
  }
};
 
console.log(user.greet());        // "Hi, I'm undefined"
console.log(user.greetCorrect()); // "Hi, I'm Alice"

Common this Pitfalls

Losing this in Callbacks

The most frequent this bug happens when passing a method as a callback:

javascriptjavascript
const counter = {
  count: 0,
  increment() {
    this.count++;
    console.log(`Count: ${this.count}`);
  }
};
 
// PROBLEM: `this` is lost
setTimeout(counter.increment, 100);
// "Count: NaN" (this.count is undefined.count + 1)
 
// SOLUTION 1: bind
setTimeout(counter.increment.bind(counter), 100);
// "Count: 1"
 
// SOLUTION 2: arrow wrapper
setTimeout(() => counter.increment(), 100);
// "Count: 2"
 
// SOLUTION 3: direct call within callback
setTimeout(function() { counter.increment(); }, 100);
// "Count: 3"

Losing this in Array Methods

javascriptjavascript
const store = {
  discount: 0.1,
  prices: [100, 200, 300],
 
  // WRONG: regular function in map loses `this`
  getDiscountedWrong() {
    return this.prices.map(function(price) {
      return price * (1 - this.discount); // `this` is undefined!
    });
  },
 
  // CORRECT: arrow function preserves `this`
  getDiscounted() {
    return this.prices.map(price => {
      return price * (1 - this.discount); // `this` = store
    });
  }
};
 
console.log(store.getDiscounted()); // [90, 180, 270]

this in Nested Objects

javascriptjavascript
const company = {
  name: "TechCorp",
  department: {
    name: "Engineering",
    getName() {
      return this.name; // `this` = department, not company
    }
  }
};
 
console.log(company.department.getName()); // "Engineering" (not "TechCorp")

Patterns for Preserving this

Pattern 1: Self / That Variable (Legacy)

javascriptjavascript
const timer = {
  seconds: 0,
  start() {
    const self = this; // Capture `this` reference
    setInterval(function() {
      self.seconds++;
      console.log(self.seconds);
    }, 1000);
  }
};

Pattern 2: Arrow Functions (Modern)

javascriptjavascript
const timer = {
  seconds: 0,
  start() {
    setInterval(() => {
      this.seconds++; // Arrow inherits `this` from start()
      console.log(this.seconds);
    }, 1000);
  }
};

Pattern 3: bind() (Explicit)

javascriptjavascript
const timer = {
  seconds: 0,
  tick() {
    this.seconds++;
    console.log(this.seconds);
  },
  start() {
    setInterval(this.tick.bind(this), 1000);
  }
};

Comparison of Approaches

PatternReadabilityModernWorks in All Contexts
Arrow functionCleanYesYes (when used in callbacks inside methods)
.bind(this)ExplicitYesYes
const self = thisVerboseLegacyYes

Practical Example: Event-Driven Shopping Cart

javascriptjavascript
class ShoppingCart {
  constructor() {
    this.items = [];
    this.taxRate = 0.08;
  }
 
  addItem(name, price, qty = 1) {
    this.items.push({ name, price, qty });
    return this;
  }
 
  removeItem(name) {
    this.items = this.items.filter(item => item.name !== name);
    return this;
  }
 
  getSubtotal() {
    return this.items.reduce((sum, item) => sum + item.price * item.qty, 0);
  }
 
  getTax() {
    return this.getSubtotal() * this.taxRate;
  }
 
  getTotal() {
    return this.getSubtotal() + this.getTax();
  }
 
  summary() {
    const lines = this.items.map(item =>
      `  ${item.name} x${item.qty}: $${(item.price * item.qty).toFixed(2)}`
    );
    lines.push(`  Subtotal: $${this.getSubtotal().toFixed(2)}`);
    lines.push(`  Tax (${(this.taxRate * 100)}%): $${this.getTax().toFixed(2)}`);
    lines.push(`  Total: $${this.getTotal().toFixed(2)}`);
    return lines.join("\n");
  }
}
 
const cart = new ShoppingCart()
  .addItem("Laptop", 999.99)
  .addItem("Mouse", 29.99, 2)
  .addItem("Keyboard", 79.99);
 
console.log(cart.summary());
// Shows itemized list with subtotal, tax, and total
// Every method uses `this` to access cart data correctly

Best Practices

  1. Use regular functions/methods for object methods (not arrow functions)
  2. Use arrow functions inside methods for callbacks that need the parent this
  3. Use bind() when you need to pass a method reference that preserves this
  4. Avoid self = this in new code; use arrow functions instead
  5. Use strict mode to catch accidental global this references (they become undefined instead of window)
Rune AI

Rune AI

Key Insights

  • Implicit binding: obj.method() sets this to obj automatically
  • Explicit binding: call, apply, and bind let you manually control what this points to
  • Arrow functions: Inherit this from their enclosing scope, making them ideal for callbacks inside methods
  • Common pitfall: Extracting a method or passing it as a callback loses this unless you use bind or an arrow wrapper
  • Strict mode: Makes default this be undefined instead of the global object, helping catch bugs early
RunePowered by Rune AI

Frequently Asked Questions

Why does JavaScript not just use the object as `this` automatically?

JavaScript's `this` is determined at call time, not definition time, because functions in JavaScript are first-class values that can be shared, copied, and passed around. A single function can be a method of multiple objects. Dynamic `this` binding allows this flexibility, though it requires developers to be intentional about how methods are called.

Is `this` the same as `self` in Python?

They are similar in concept (both refer to the current instance) but different in behavior. Python's `self` is an explicit parameter you always pass and see in the function signature. JavaScript's `this` is an implicit keyword determined by how the function is called. Python's `self` is always the instance; JavaScript's `this` can change based on the calling pattern.

Can I completely avoid using `this` in JavaScript?

You can minimize `this` by using factory functions with [closures](/tutorials/programming-languages/javascript/javascript-function-scope-local-vs-global-scope) instead of classes and constructors. Factory functions access data through closure [variables](/tutorials/programming-languages/javascript/js-variables-guide-how-to-declare-and-use-them) instead of `this`, eliminating binding issues entirely. However, you lose `instanceof` checks and prototype-based method sharing.

What does `this` refer to in the global scope?

In a browser, global `this` refers to the `window` object. In Node.js, it refers to the `global` object (or `module.exports` in CommonJS modules). In strict mode and ES modules, global `this` is `undefined` at the top level. This inconsistency is another reason to always use `this` inside objects or classes, not at the global level.

How does `this` work with class fields?

Class fields (properties defined with `=` directly in the class body) can use arrow functions to auto-bind `this` to the instance. For example: `handleClick = () => { console.log(this); }`. This creates a new function for each instance but guarantees `this` always refers to the instance, even when the method is extracted as a callback.

Conclusion

The this keyword in JavaScript is context-dependent. Implicit binding works when calling methods directly on objects, explicit binding (call, apply, bind) forces a specific context, new binding creates instances, and arrow functions lock in the surrounding scope's this. Most this bugs come from passing methods as callbacks without preserving their context. Once you understand the four binding rules and adopt arrow functions for nested callbacks, this becomes predictable and manageable.