Skip to content

this in Function Calls: Deep Dive into Call Stack and Execution Context

Imagine you're making a phone call. When you connect the call, the phone system needs to know "who you are," "where you are," and "who you're trying to reach." JavaScript function calls work similarly—when a function is called, the JavaScript engine needs to determine who this should point to.

This pointing is entirely determined by how the function is called, not by how the function is defined. This is the core principle for understanding this.

Default Binding: Independent Function Calls

When we call a function directly, "default binding" occurs. In this situation, where this points depends on whether we're in strict mode.

Default Binding in Non-Strict Mode

javascript
function showThis() {
  console.log(this);
  console.log(this === window); // Browser environment
}

showThis(); // window object

In non-strict mode, this in independent function calls points to the global object (window in browsers, global in Node.js).

Default Binding in Strict Mode

javascript
"use strict";

function showThisStrict() {
  console.log(this); // undefined
}

showThisStrict(); // undefined

In strict mode, this in independent function calls is undefined, which helps avoid accidental global pollution.

javascript
"use strict";

function unintentionalGlobal() {
  // This line will throw an error, not create a global variable
  this.newGlobal = "I accidentally created a global variable"; // TypeError: Cannot set property 'newGlobal' of undefined
}

unintentionalGlobal();

Implicit Binding: Called as Object Method

When a function is called as a method of an object, this points to the object that called that method.

Basic Implicit Binding

javascript
const person = {
  name: "John",
  greet: function () {
    console.log(`Hello, I'm ${this.name}`);
  },
};

person.greet(); // "Hello, I'm John"

In this example, when person.greet() is called, this inside the greet function points to the person object.

this in Chained Calls

javascript
const calculator = {
  value: 0,
  add: function (num) {
    this.value += num;
    return this; // Return this to support chaining
  },
  subtract: function (num) {
    this.value -= num;
    return this;
  },
  getResult: function () {
    return this.value;
  },
};

const result = calculator.add(10).add(5).subtract(3);
console.log(calculator.getResult()); // 12
console.log(result.getResult()); // 12 (result and calculator point to the same object)

this in Nested Objects

javascript
const company = {
  name: "TechCorp",
  departments: {
    engineering: {
      manager: "Sarah",
      introduce: function () {
        console.log(`I'm ${this.name} from ${this.manager} department`);
      },
    },
  },
};

company.departments.engineering.introduce();
// Output: "I'm undefined from Sarah department"

There's a problem here: this.name is undefined because this points to the company.departments.engineering object, not the company object.

Lost Implicit Binding

Implicit binding is easily lost—one of the most common this traps.

Loss Due to Variable Assignment

javascript
const person = {
  name: "Emma",
  greet: function () {
    console.log(`Hello, I'm ${this.name}`);
  },
};

const greet = person.greet; // Assign method to variable
greet(); // "Hello, I'm undefined" (this points to global object or undefined)

Loss in Callback Functions

javascript
const user = {
  name: "David",
  age: 30,
  showInfo: function () {
    console.log(`${this.name} is ${this.age} years old`);
  },
};

// 1. Loss in setTimeout
setTimeout(user.showInfo, 1000); // "undefined is undefined years old"

// 2. Loss in array methods
const users = [user];
users.forEach((u) => u.showInfo()); // Works normally
users.forEach(function (item) {
  item.showInfo(); // Works normally
});

// But this will lose this
const showUserInfo = user.showInfo;
users.forEach(() => showUserInfo()); // "undefined is undefined years old"

Loss in Event Handling

javascript
const button = {
  text: "Click me",
  handleClick: function () {
    console.log(`Button clicked: ${this.text}`);
  },
};

// Add event listener
document.querySelector("button").addEventListener("click", button.handleClick);
// Output: "Button clicked: undefined" (this points to button element, not button object)

Explicit Binding: Using call, apply, bind

To solve the problem of lost implicit binding, JavaScript provides three methods to explicitly specify where this points.

The call Method

The call method can immediately call a function and specify the this binding:

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

const person1 = { name: "Alice" };
const person2 = { name: "Bob" };

introduce.call(person1, "Hello", "!"); // "Hello, I'm Alice!"
introduce.call(person2, "Hi", "."); // "Hi, I'm Bob."

The apply Method

The apply method is similar to call, but parameters are passed as an array:

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

const person = { name: "Charlie" };
introduce.apply(person, ["Hello", "!"]); // "Hello, I'm Charlie!"

apply is commonly used in practical applications for handling variable parameters:

javascript
// Find maximum value in an array
const numbers = [5, 8, 2, 9, 1];
const max = Math.max.apply(null, numbers); // 9
const min = Math.min.apply(null, numbers); // 1

// Modern syntax (using spread operator)
const max2 = Math.max(...numbers); // 9
const min2 = Math.min(...numbers); // 1

The bind Method

The bind method creates a new function, permanently binding this to a specified object:

javascript
const person = {
  name: "Frank",
  greet: function () {
    console.log(`Hello, I'm ${this.name}`);
  },
};

// Create permanently bound function
const greetPerson = person.greet.bind(person);
greetPerson(); // "Hello, I'm Frank"

// Even if reassigned, this won't change
const anotherPerson = { name: "Grace" };
greetPerson.call(anotherPerson); // "Hello, I'm Frank" (still Frank)

Practical application scenarios of bind:

javascript
// 1. Fix this in event handlers
const button = {
  text: "Submit",
  handleClick: function () {
    console.log(`${this.text} button was clicked`);
  },
};

document
  .querySelector("button")
  .addEventListener("click", button.handleClick.bind(button));

// 2. Preset parameters
function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2); // Preset first parameter as 2
console.log(double(5)); // 10
console.log(double(10)); // 20

const triple = multiply.bind(null, 3); // Preset first parameter as 3
console.log(triple(4)); // 12

new Binding: Constructor Function Calls

When using the new operator to call a function, this points to the newly created object.

Basic Usage of Constructor Functions

javascript
function Person(name, age) {
  // this points to the newly created object
  this.name = name;
  this.age = age;
  this.greet = function () {
    console.log(`Hello, I'm ${this.name}, ${this.age} years old`);
  };
}

const john = new Person("John", 25);
const mary = new Person("Mary", 23);

console.log(john.name); // "John"
console.log(mary.age); // 23
john.greet(); // "Hello, I'm John, 25 years old"
mary.greet(); // "Hello, I'm Mary, 23 years old"

return in Constructor Functions

javascript
function ConstructorExample(name) {
  this.name = name;

  // Case 1: Don't return anything
  // const obj1 = new ConstructorExample('test1');
  // obj1.name === 'test1' ✅

  // Case 2: Return primitive type
  // return 'some string';
  // const obj2 = new ConstructorExample('test2');
  // obj2.name === 'test2' ✅ (ignore return value)

  // Case 3: Return object
  return { customName: "custom object" };
  // const obj3 = new ConstructorExample('test3');
  // obj3.name === undefined ❌
  // obj3.customName === 'custom object' ✅ (returned object overrides this)
}

this Traps in Constructor Functions

javascript
function User(name) {
  this.name = name;

  // Common mistake: forgetting the new keyword
  // const user = User('John'); // In non-strict mode, this points to global object
  // window.name === 'John' ❌ Global pollution
}

// Safe constructor function pattern
function SafeUser(name) {
  // Check if called with new
  if (!(this instanceof SafeUser)) {
    return new SafeUser(name);
  }

  this.name = name;
}

const user1 = new SafeUser("Alice"); // Normal
const user2 = SafeUser("Bob"); // Automatically uses new

this in Arrow Functions

Arrow functions don't have their own this binding; they capture the this from the scope where they were defined.

Basic Behavior of Arrow Functions

javascript
const person = {
  name: "Henry",

  // Regular function
  regularGreet: function () {
    console.log(this.name); // "Henry"

    const arrowGreet = () => {
      console.log(this.name); // "Henry" (inherits outer this)
    };

    arrowGreet();
  },

  // Arrow function as method
  arrowGreet: () => {
    console.log(this.name); // undefined (inherits global scope's this)
  },
};

person.regularGreet(); // Normal output
person.arrowGreet(); // undefined

Practical Applications of Arrow Functions

javascript
// 1. Use in timers
const timer = {
  seconds: 0,
  start: function () {
    setInterval(() => {
      this.seconds++;
      console.log(`Timer: ${this.seconds} seconds`);
    }, 1000);
  },
};

timer.start(); // Works correctly, this points to timer object

// 2. Use in event handlers
const app = {
  name: "MyApp",
  init: function () {
    document.querySelector("button").addEventListener("click", () => {
      console.log(`${this.name} was clicked`); // this points to app object
    });
  },
};

app.init();

// 3. Use in array methods
const calculator = {
  total: 0,
  numbers: [1, 2, 3, 4, 5],
  sum: function () {
    this.total = this.numbers.reduce((sum, num) => {
      return sum + num;
    }, 0);
    console.log(`Total: ${this.total}`);
  },
};

calculator.sum(); // "Total: 15"

Priority of Binding Rules

When multiple binding rules exist simultaneously, there's a clear priority order:

new binding > explicit binding > implicit binding > default binding
javascript
function showThis() {
  console.log(this.name);
}

const obj1 = { name: "Object1" };
const obj2 = { name: "Object2" };

// Default binding
showThis(); // undefined (strict mode)

// Implicit binding
obj1.showThis = showThis;
obj1.showThis(); // "Object1"

// Explicit binding vs implicit binding
obj1.showThis.call(obj2); // "Object2" (explicit binding has higher priority)

// new binding vs explicit binding
const boundShowThis = showThis.bind(obj1);
const instance = new boundShowThis(); // this points to newly created object, not obj1
console.log(instance); // {}

Best Practices in Practical Applications

1. Use Arrow Functions to Avoid this Issues

javascript
// ✅ Good practice
class Component {
  constructor() {
    this.data = [];
    this.load();
  }

  load() {
    fetch("/api/data")
      .then((response) => response.json())
      .then((data) => {
        this.data = data; // Arrow function maintains this binding
        this.render();
      });
  }

  render = () => {
    // Class property arrow function
    console.log("Rendering data:", this.data);
  };
}

// ❌ Problematic approach
class ProblemComponent {
  constructor() {
    this.data = [];
    this.load();
  }

  load() {
    fetch("/api/data")
      .then(function (response) {
        return response.json();
      })
      .then(function (data) {
        this.data = data; // this points to wrong object
        this.render(); // render method doesn't exist
      });
  }
}

2. Use bind to Explicitly Specify this

javascript
class EventEmitter {
  constructor() {
    this.handlers = {};
  }

  on(event, handler) {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    // Auto-bind this
    this.handlers[event].push(handler.bind(this));
  }

  emit(event, data) {
    if (this.handlers[event]) {
      this.handlers[event].forEach((handler) => handler(data));
    }
  }
}

const emitter = new EventEmitter();
emitter.on("data", function (data) {
  console.log("Received data:", data); // this correctly points to emitter instance
});
emitter.emit("data", "test data");

3. Avoid Losing this in Callbacks

javascript
const user = {
  name: "John",
  friends: ["Alice", "Bob", "Charlie"],

  // ❌ Wrong approach
  printFriendsWrong: function () {
    this.friends.forEach(function (friend) {
      console.log(`${this.name}'s friend: ${friend}`); // this points to wrong object
    });
  },

  // ✅ Correct approach 1: Use arrow function
  printFriendsArrow: function () {
    this.friends.forEach((friend) => {
      console.log(`${this.name}'s friend: ${friend}`); // this is correct
    });
  },

  // ✅ Correct approach 2: Use bind
  printFriendsBind: function () {
    this.friends.forEach(
      function (friend) {
        console.log(`${this.name}'s friend: ${friend}`); // this is correct
      }.bind(this)
    );
  },

  // ✅ Correct approach 3: Save this reference
  printFriendsSaved: function () {
    const self = this;
    this.friends.forEach(function (friend) {
      console.log(`${self.name}'s friend: ${friend}`); // Use saved this
    });
  },
};

Summary

Understanding the behavior of this in function calls is a core skill in JavaScript development. Remember these key points:

  1. Default binding: Independent function calls point to global object (non-strict mode) or undefined (strict mode)
  2. Implicit binding: When called as object method, points to calling object, but easily lost
  3. Explicit binding: Use call, apply, bind to explicitly specify this
  4. new binding: Constructor function calls point to newly created object
  5. Arrow functions: Inherit this from definition scope, don't have their own this

Practical advice:

  • When unsure about this binding, use explicit binding
  • Prioritize arrow functions in callbacks and event handlers
  • When writing constructor functions, check if new operator is used
  • Avoid accidentally losing this binding in callbacks

Mastering this knowledge allows you to accurately predict and control this behavior in various complex scenarios, writing more robust JavaScript code. Next, we'll dive deep into the specific applications and advanced patterns of this in object methods.