Skip to main content

Command Palette

Search for a command to run...

Understanding the this Keyword in JavaScript

Updated
9 min readView as Markdown

A complete guide — from the problem it solves to how the JavaScript engine resolves it internally.

In this article

  1. Why this was introduced

  2. What this actually represents

  3. this in the global context

  4. this inside objects

  5. this inside regular functions

  6. How calling context changes this

  7. Arrow functions and this

  8. Explicitly binding this — call, apply, bind

  9. The internal mechanism — how the engine resolves it

Why this was introduced

Imagine you are writing a function that needs to access some data — data that belongs to an object. The function works fine on its own, but the moment you attach it to an object, a problem appears: how does the function know which object it belongs to at runtime?

Consider this scenario. You have a car object and a bus object. Both have a speed property. You want to write a single describe() function that prints the speed of whichever vehicle calls it. Without this, the function has no way to know which vehicle it is working with — you would have to pass the object in as an argument every single time.

Without this — the old, painful way

function describe(vehicle) {
  console.log("Speed: " + vehicle.speed);
}

describe(car);   // you always have to pass the object manually
describe(bus);

This approach is repetitive and error-prone. The this keyword was introduced to solve exactly this problem — it gives a function a way to refer to the object that called it, automatically, without needing to pass it explicitly.

The core insight

The problem was never that functions didn't work. The problem was that functions had no way to know which object's data they should be working with. this fills that gap.

2. What this actually represents

this is a keyword that refers to the execution context — specifically, the object that is currently "owning" or "calling" the function being executed. It is not fixed when the function is defined. It is determined dynamically, at the moment the function is called.

Key rule: The value of this is not about where the function is written. It is about how and where the function is called.

This is the single most important thing to understand about this, and it is the source of most confusion for developers coming from other languages.

3. this in the global context

When you use this outside of any function or object — at the top level of your script — it refers to the global object.

In a browser

console.log(this);         // Window { ... }
console.log(this === window); // true

In Node.js

console.log(this);  // {} (the module's exports object at top level)
                   // Inside a module, this is not globalThis

Heads up

In the browser, the global object is window. In Node.js, the global object is globalThis — but at the top level of a Node module, this points to module.exports, not globalThis. Always check your runtime.

4. this inside objects

When a function is defined as a method on an object and you call it through that object, this refers to that object.

Method context

const car = {
  brand: "Toyota",
  speed: 120,
  describe() {
    // 'this' here refers to the car object
    console.log(`\({this.brand} is going \){this.speed} km/h`);
  }
};

car.describe(); // "Toyota is going 120 km/h"

The reason this points to car here is that the call site is car.describe() — the function is called through the car object. This is called implicit binding

5. this inside regular functions

When a function is called without any object in front of it (a plain function call), things get interesting. The behavior depends on whether you are in strict mode or not.

Non-strict mode

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

showThis(); // Window (in browser) — defaults to global object

Strict mode

"use strict";

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

showThis(); // undefined — no implicit global fallback

Common mistake

Extracting a method from an object and calling it as a standalone function loses the binding. The function is the same, but the calling context changes — so this changes too.

const car = { brand: "Toyota", describe() { console.log(this.brand); } };

const fn = car.describe;
fn(); // undefined — 'this' is now the global object, not car

6. How calling context changes this

The calling context is the most important factor that determines what this will be. JavaScript uses a set of rules — applied in order of priority — to resolve it.

Call pattern What this resolves to
new Foo() The newly created object (new binding)
fn.call(obj) obj — explicit binding overrides everything
fn.apply(obj) obj — same as call, but args as array
fn.bind(obj)() obj — permanently bound
obj.method() obj — implicit binding
fn() Global object / undefined in strict mode (default binding)
() => {} Lexically inherited from surrounding scope (arrow function)

7. Arrow functions and this

Arrow functions do not have their own this. Instead, they inherit this from the lexical scope — the scope in which they were written. This is called lexical binding.

Arrow function captures outer this

const timer = {
  count: 0,
  start() {
    // 'this' here is the timer object (method call)
    setInterval(() => {
      // Arrow function: 'this' is inherited from start()
      this.count++;
      console.log(this.count);
    }, 1000);
  }
};

timer.start(); // Works! Logs: 1, 2, 3 ...

If you used a regular function inside setInterval, this would be the global object (or undefined in strict mode) and you would lose the reference to timer. Arrow functions solve this elegantly.

8. Explicitly binding this — call, apply, bind

JavaScript gives you three methods to take full control of what this will be when a function runs.

call() invoke immediately with a specific this

call

function greet(greeting) {
  console.log(`\({greeting}, I am \){this.name}`);
}

const user = { name: "Arjun" };

greet.call(user, "Hello"); // "Hello, I am Arjun"

apply() same as call, but arguments as an array

apply

greet.apply(user, ["Hi"]); // "Hi, I am Arjun"

bind() returns a new permanently-bound function

bind

const greetArjun = greet.bind(user);
greetArjun("Hey"); // "Hey, I am Arjun"
// No matter how greetArjun is called, this is always user

9. The internal mechanism — how the engine resolves this

Here is where it gets really interesting. How does the JavaScript engine actually figure out what this is? It uses a concept called the Execution Context.

The Execution Context

Every time a function is called, the JavaScript engine creates an Execution Context — a container that holds everything the function needs to run. It has three main parts:

Execution context (conceptual structure)

{
  // 1. Variable Environment — all variables declared inside the function
  variableEnvironment: { ... },

  // 2. Lexical Environment — the scope chain (outer references)
  lexicalEnvironment: { ... },

  // 3. This Binding — the value of 'this' for this call
  thisBinding: <determined by how the function was called>
}

The engine pushes this Execution Context onto the Call Stack when the function is invoked, and pops it off when the function returns. The thisBinding is set at the moment the context is created — before a single line of the function body runs.

The Reference Type — the internal trick

Internally, when you write car.describe(), the JavaScript engine does not immediately see a "function call". It first evaluates car.describe as a Reference — an internal data structure that looks like this:

Internal Reference type (not real JS — this is the engine's internal representation)

{
  base: car,         // the object the property was accessed on
  name: "describe", // the property name
  strict: false      // whether strict mode is active
}

When the engine sees the () after this reference, it checks if the reference has a base value. If it does, it sets this to that base — in this case, car. If you just call a plain function like describe(), the reference has no meaningful base, so this falls back to the global object (or undefined in strict mode).

The four binding rules — priority order

The engine applies exactly these four rules, in this priority order, to determine this:

Priority 1 — new binding (highest)

function Person(name) {
  this.name = name; // 'this' = the brand new object being created
}
const p = new Person("Priya");
// Engine: creates a new empty object, sets this = that object,
// runs the function body, then returns the object

Priority 2 — explicit binding (call / apply / bind)

function show() { console.log(this.x); }
show.call({ x: 42 }); // 42 — you explicitly told the engine what 'this' is

Priority 3 — implicit binding (method call)

obj.method(); // 'this' = obj — the engine reads the base from the reference

Priority 4 — default binding (lowest)

show(); // 'this' = window (non-strict) or undefined (strict)

Arrow functions bypass all four rules

Arrow functions are special. They do not participate in the four-rule system at all. When the engine creates an arrow function, it captures the thisBinding from the surrounding Execution Context at the time the arrow function is defined — not when it is called. This value is then permanently frozen into the arrow function's internal [[ThisMode]] slot.

Arrow function internal behavior

const obj = {
  value: 10,
  getArrow() {
    // At this point, this = obj (implicit binding of getArrow)
    return () => {
      // The arrow captures 'this' from getArrow's execution context
      // Even if you do: const f = obj.getArrow(); f.call(somethingElse);
      // 'this' inside the arrow will STILL be obj
      console.log(this.value);
    };
  }
};

const f = obj.getArrow();
f.call({ value: 999 }); // Still logs 10 — call() cannot override arrow's this

Summary

The this keyword exists to give functions a way to refer to their calling context. Its value is never fixed at definition time for regular functions — it is resolved dynamically when the function is called, using four rules applied in priority order: new binding, explicit binding, implicit binding, and default binding. Arrow functions are the exception — they inherit this lexically and cannot be overridden.

Understanding this deeply means understanding that JavaScript is evaluating a Reference internally — and the base of that reference is what becomes this. Once that clicks, the behaviour stops feeling like magic and starts making complete sense.