Skip to main content
๐Ÿ‘†intermediate

Understanding `this`

The this keyword is one of JavaScript's most misunderstood features. Learn the 5 rules that determine what this refers to โ€” and how arrow functions change everything.

What is this?

this is a special keyword that refers to an object โ€” but which object depends entirely on how the function is called, not where it's defined. This is the key insight.

There are 5 rules (in order of precedence) that determine what this is:

  1. new binding
  2. Explicit binding (call, apply, bind)
  3. Implicit binding (method call)
  4. Default binding (standalone call)
  5. Arrow function (lexical binding โ€” no own this)

Rule 1: Default Binding

When a function is called as a plain function (no object context), this defaults to globalThis (window in browsers) in non-strict mode, or undefined in strict mode:

hljs javascript
function showThis() {
  console.log(this);
}

showThis(); // window (browser) or global (Node) or undefined (strict mode)

// In strict mode ("use strict"):
function showStrict() {
  "use strict";
  console.log(this); // undefined
}
showStrict();

โœ…Tip

Modern JavaScript (ES modules) and class bodies are always in strict mode. So a standalone function call gives this === undefined.

Rule 2: Implicit Binding

When a function is called as a method of an object, this is the object before the dot:

hljs javascript
const user = {
  name: "Alice",
  greet() {
    return `Hello, I'm ${this.name}`;
  },
};

console.log(user.greet()); // "Hello, I'm Alice"
// this === user

The pitfall โ€” losing this context:

hljs javascript
const greet = user.greet; // extract the function
console.log(greet()); // "Hello, I'm undefined"
// this is no longer user โ€” it's the global object!

// Common trap with callbacks:
const buttons = {
  label: "Click me",
  setup() {
    // 'this' here is buttons
    setTimeout(function() {
      // 'this' here is window! (callback loses context)
      console.log(this.label); // undefined
    }, 100);
  },
};

Rule 3: Explicit Binding

Use call, apply, or bind to explicitly set this:

hljs javascript
function introduce(greeting, punctuation) {
  return `${greeting}, I'm ${this.name}${punctuation}`;
}

const person = { name: "Bob" };

// call โ€” arguments passed individually
introduce.call(person, "Hello", "!");   // "Hello, I'm Bob!"

// apply โ€” arguments passed as array
introduce.apply(person, ["Hey", "."]);  // "Hey, I'm Bob."

// bind โ€” creates a NEW function with this permanently bound
const bobIntro = introduce.bind(person);
bobIntro("Hi", "?"); // "Hi, I'm Bob?"

// bind can also pre-fill arguments (partial application)
const bobHello = introduce.bind(person, "Hello");
bobHello("!"); // "Hello, I'm Bob!"
bobHello("?"); // "Hello, I'm Bob?"

Rule 4: new Binding

When a function is called with new, a new object is created and this refers to that new object:

hljs javascript
function Car(make, model) {
  this.make = make;    // this = new object being created
  this.model = model;
  this.toString = function() {
    return `${this.make} ${this.model}`;
  };
}

const toyota = new Car("Toyota", "Camry");
const honda = new Car("Honda", "Civic");

console.log(toyota.make);   // "Toyota"
console.log(String(toyota)); // "Toyota Camry"
console.log(String(honda));  // "Honda Civic"

new binding overrides all other rules.

Rule 5: Arrow Functions (Lexical this)

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

hljs javascript
const timer = {
  seconds: 0,

  // โœ— Regular function โ€” loses this
  startBroken() {
    setInterval(function() {
      this.seconds++; // this is window, not timer!
      console.log(this.seconds);
    }, 1000);
  },

  // โœ“ Arrow function โ€” lexically inherits this
  start() {
    setInterval(() => {
      this.seconds++; // this is timer โœ“
      console.log(this.seconds);
    }, 1000);
  },
};

Arrow functions are the modern fix for this context loss in callbacks.

The 5-Rule Precedence

When determining this, check rules in this order:

hljs javascript
// 1. new binding
const obj1 = new Foo(); // this = new empty object

// 2. Explicit: call/apply/bind
foo.call(obj2); // this = obj2
const bound = foo.bind(obj3); // this = obj3

// 3. Implicit: method call
obj4.foo(); // this = obj4

// 4. Default: standalone
foo(); // this = undefined (strict) or global

// 5. Arrow: lexical (none of the above apply)
const arrowFn = () => this; // this = whatever outer scope's this is

Common Patterns

Saving this as a variable (old pattern)

hljs javascript
const self = this; // or: const that = this;
setTimeout(function() {
  console.log(self.name); // use saved reference
}, 100);

Arrow function fix (modern pattern)

hljs javascript
setTimeout(() => {
  console.log(this.name); // arrow inherits this
}, 100);

Explicit bind in constructors

hljs javascript
class Button {
  constructor() {
    this.count = 0;
    // Bind in constructor so the method always has correct this
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.count++;
    console.log("Clicked", this.count, "times");
  }
}

Class field arrow function (modern React pattern)

hljs javascript
class Button {
  count = 0;

  // Class field with arrow function โ€” automatically bound
  handleClick = () => {
    this.count++;
    console.log("Clicked", this.count, "times");
  };
}

this in Classes

In class methods, this refers to the instance when called properly:

hljs javascript
class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `Hi, I'm ${this.name}`;
  }

  delayedGreet() {
    // Arrow captures this from the method's context
    setTimeout(() => {
      console.log(this.greet());
    }, 100);
  }
}

const alice = new Person("Alice");
alice.greet(); // "Hi, I'm Alice"
alice.delayedGreet(); // Works! "Hi, I'm Alice"

// Detaching the method loses this:
const fn = alice.greet;
// fn(); // Error: Cannot read property 'name' of undefined (strict mode)
โ–ถTry it yourself

Key Takeaways

  • this is determined by how a function is called, not where it's defined
  • 5 rules: new > explicit (call/apply/bind) > method call > standalone > arrow (lexical)
  • Arrow functions don't have their own this โ€” they inherit from the enclosing scope
  • Detaching a method from its object breaks implicit binding
  • Use arrow functions for callbacks to preserve this context
  • Use .bind() to permanently attach a this value

Ready to test your knowledge?

Take a quiz on what you just learned.

Take the Quiz โ†’