Execution Context in JavaScript - Exercise 3

These methods explicitly choose the receiver for a regular function call; bind returns a new function.

function introduce() {
  return `${this.name} is ready`;
}
const user = { name: 'Ava' };
console.log(introduce.call(user));
console.log(introduce.bind(user)());

Arrow functions capture this from their surrounding lexical environment and ignore replacement by call, apply, or bind.

const user = {
  name: 'Mina',
  show: () => this.name
};
console.log(user.show()); // not user.name

A constructor call creates a new object, sets its prototype, and calls the constructor with this bound to that new object.

function User(name) {
  this.name = name;
}
const user = new User('Sam');
console.log(user.name);

Each recursive call gets a new context with its own parameters and local bindings on the call stack.

function factorial(number) {
  if (number <= 1) return 1;
  return number * factorial(number - 1);
}
console.log(factorial(3));
// contexts: factorial(3) -> factorial(2) -> factorial(1)

Modules have their own module environment, do not attach top-level bindings to window, and use undefined for top-level this.

// main.js loaded with type="module"
const secret = 'private';
console.log(this); // undefined
// secret is not a window property.

A stack trace lists the active function calls, helping you locate the path that led to an error.

function load() {
  validate();
}
function validate() {
  console.trace('validation path');
}
load();

Unbounded recursion creates contexts faster than they can return, exhausting the call stack.

function walk() {
  walk();
}
// walk(); // RangeError: Maximum call stack size exceeded

A closure preserves access to an outer environment after its original function context has returned.

function makeIdGenerator() {
  let id = 0;
  return () => ++id;
}
const nextId = makeIdGenerator();
console.log(nextId(), nextId());