Module: JavaScript
JavaScript·051·4 MIN READ

051: The `this` Keyword, call, apply, bind, and Function Borrowing

TOPICS COVERED: The `this` Keyword, call, apply, bind, and Function Borrowing

Outcomes

By the end of this lesson, you can:

  • explain that this is determined by how a normal function is called;
  • distinguish method calls, plain calls, constructor calls, and explicit binding;
  • explain why arrow functions do not have their own this;
  • use call(), apply(), and bind() intentionally;
  • recognize function borrowing;
  • avoid losing method context in callbacks and event handlers.

Mental Model: this Belongs to the Call Site

For normal functions, do not ask only "where was this function written?" Ask: how was it called?

js
const order = {
  id: "ORD-101",
  print() {
    console.log(this.id);
  },
};

order.print(); // "ORD-101"

The receiver before the dot becomes this for that call.

Plain Function Calls

In strict mode:

js
"use strict";

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

showThis(); // undefined

Do not depend on the older sloppy-mode behavior where this may fall back to the global object.

Method Calls

js
const branch = {
  name: "Anna Nagar",
  describe() {
    return `Branch: ${this.name}`;
  },
};

console.log(branch.describe());

But extracting the method changes the call site:

js
const describe = branch.describe;

describe(); // `this` is not `branch`

This is one of the most common this bugs.

Arrow Functions

Arrow functions capture this lexically from the surrounding scope.

js
const counter = {
  count: 0,

  start() {
    setTimeout(() => {
      this.count += 1;
      console.log(this.count);
    }, 100);
  },
};

counter.start();

The arrow is useful because it preserves the surrounding method's this.

Do not use an arrow as an object method when you expect dynamic method this:

js
const user = {
  name: "Maya",
  greet: () => {
    console.log(this.name);
  },
};

The arrow does not get user as its this.

Constructor Calls

With new, JavaScript creates a new object and binds it as this.

js
function Product(name, price) {
  this.name = name;
  this.price = price;
}

const item = new Product("Tea", 20);
console.log(item.name);

Modern code often uses class syntax, but understanding constructor calls helps explain the object model.

Explicit Binding with call

js
function describe(prefix) {
  return `${prefix}: ${this.name}`;
}

const product = { name: "Biryani" };

console.log(describe.call(product, "Product"));

call invokes immediately and accepts arguments individually.

Explicit Binding with apply

js
function total(a, b, c) {
  return this.base + a + b + c;
}

const context = { base: 10 };

console.log(total.apply(context, [1, 2, 3]));

apply invokes immediately and takes arguments as an array-like collection.

Spread syntax often makes call more readable today:

js
const values = [1, 2, 3];
total.call(context, ...values);

Permanent Binding with bind

bind returns a new function.

js
const printer = {
  prefix: "ORDER",

  print(id) {
    console.log(`${this.prefix}-${id}`);
  },
};

const printOrder = printer.print.bind(printer);

printOrder(101);

This is useful when handing a method to another API.

Event Handler Context

Traditional DOM listener functions receive the current target as this:

js
button.addEventListener("click", function () {
  console.log(this === button); // true
});

But production code is often clearer when it uses the event object explicitly:

js
button.addEventListener("click", (event) => {
  console.log(event.currentTarget);
});

Relying on event.currentTarget usually makes the dependency more obvious.

Function Borrowing

A method can be used with another compatible object.

js
const formatter = {
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
};

const customer = {
  firstName: "Anu",
  lastName: "Raj",
};

console.log(formatter.fullName.call(customer));

The borrowed function does not care where it was originally stored; it cares about the call context.

Worked Example: Preserve a Class Method Callback

js
class CartController {
  constructor(button) {
    this.button = button;
    this.items = [];

    this.handleAdd = this.handleAdd.bind(this);
    this.button.addEventListener("click", this.handleAdd);
  }

  handleAdd() {
    this.items.push({ id: crypto.randomUUID() });
    console.log(this.items.length);
  }

  destroy() {
    this.button.removeEventListener("click", this.handleAdd);
  }
}

Binding once in the constructor creates a stable function reference that can later be removed.

An alternative is a class-field arrow in environments/toolchains that support the syntax you target:

js
class CartController {
  handleAdd = () => {
    this.items.push({ id: crypto.randomUUID() });
  };
}

Failure Example: Binding During Removal

js
button.addEventListener("click", controller.handleAdd.bind(controller));

button.removeEventListener("click", controller.handleAdd.bind(controller));

These two bind() calls create two different function objects. The listener is not removed.

Store the bound function once.

this Decision Table

Call formTypical this
fn()undefined in strict mode
obj.fn()obj
fn.call(obj)obj
fn.apply(obj)obj
fn.bind(obj)bound object when returned function runs
new Fn()newly created instance
arrow functioninherited lexical this

Advanced Notes: Method Extraction, Partial Application, and API Design

A useful way to avoid this bugs is to ask whether the behavior really needs an implicit receiver.

Compare:

js
const cart = {
  taxRate: 0.18,

  totalWithTax(subtotal) {
    return subtotal + subtotal * this.taxRate;
  },
};

with a dependency-explicit function:

js
function totalWithTax(subtotal, taxRate) {
  return subtotal + subtotal * taxRate;
}

The second is easier to reuse and test because its dependencies are visible. Use this when object-oriented stateful behavior genuinely improves the model, not merely because methods are available.

bind() can pre-fill arguments too

js
function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);

console.log(double(5)); // 10

This is partial application. It can be convenient, though closures are often clearer:

js
const double = (value) => multiply(2, value);

Method extraction test

Before passing a method as a callback, ask whether it reads this.

js
class Reporter {
  constructor(prefix) {
    this.prefix = prefix;
  }

  report(message) {
    console.log(this.prefix, message);
  }
}

const reporter = new Reporter("[APP]");

// Unsafe:
setTimeout(reporter.report, 0, "started");

// Safe:
setTimeout(reporter.report.bind(reporter), 0, "started");

When a callback API passes its own arguments or receiver, explicit wrappers can make the boundary clearer.

Interview exercise

Explain why these differ:

js
const object = {
  value: 42,

  normal() {
    return this.value;
  },

  arrow: () => this.value,
};

Then explain why:

js
const normal = object.normal;
normal();

does not remember object.

The goal is not to memorize slogans. The goal is to reason from the call form.

Mistakes and Debugging

  • Treating this as lexical in normal functions.
  • Assuming a method remembers its object after extraction.
  • Using arrow methods when dynamic this is required.
  • Rebinding a function repeatedly and losing the original reference.
  • Using this when a plain parameter would make dependencies clearer.

Debug the call site:

js
console.log("this =", this);
console.trace();

Best Practices

  • Prefer explicit parameters when context does not need to be dynamic.
  • Use methods when behavior naturally belongs to an object.
  • Use arrows to preserve surrounding lexical this.
  • Bind once when stable callback identity matters.
  • Prefer event.currentTarget over implicit event-handler this for clarity.

Exercises

Core

Predict this in method, plain function, arrow, call, and new examples.

Practice

Repair:

js
const account = {
  balance: 100,
  show() {
    console.log(this.balance);
  },
};

setTimeout(account.show, 0);

Professional Extension

Build a controller with:

  • mount();
  • destroy();
  • a bound click handler;
  • a test proving the listener is removed.

Recap

this is primarily a call-site concept for normal functions. call, apply, and bind let you control it explicitly. Arrow functions are different because they capture the surrounding this.