javascript

examples

examples.js
/**
 * =====================================================
 * 5.5 CALL, APPLY, AND BIND - EXAMPLES
 * =====================================================
 * Controlling function context
 */

// =====================================================
// 1. UNDERSTANDING 'THIS' CONTEXT
// =====================================================

console.log("--- Understanding 'this' Context ---");

const user = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  },
};

// Method call - 'this' is the object
user.greet(); // "Hello, Alice"

// Extracted function - 'this' is lost
const greet = user.greet;
// greet();  // "Hello, undefined" (strict mode: error)

console.log('This is why we need call/apply/bind!');

// =====================================================
// 2. THE CALL() METHOD
// =====================================================

console.log('\n--- The call() Method ---');

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

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

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

// Simple example
function sayName() {
  console.log('My name is ' + this.name);
}

sayName.call({ name: 'Charlie' }); // "My name is Charlie"

// =====================================================
// 3. THE APPLY() METHOD
// =====================================================

console.log('\n--- The apply() Method ---');

// Same function, using apply
introduce.apply(person1, ['Hey', '??']); // "Hey, I'm Alice??"

// apply is useful with arrays
const numbers = [5, 6, 2, 3, 7];

// Using apply with Math.max
const max = Math.max.apply(null, numbers);
const min = Math.min.apply(null, numbers);

console.log('Max:', max); // 7
console.log('Min:', min); // 2

// Modern alternative with spread
console.log('Max (spread):', Math.max(...numbers));

// =====================================================
// 4. CALL VS APPLY COMPARISON
// =====================================================

console.log('\n--- call() vs apply() ---');

function logInfo(role, department) {
  console.log(`${this.name}: ${role} in ${department}`);
}

const employee = { name: 'Alice' };

// call: individual arguments (C = Comma)
logInfo.call(employee, 'Developer', 'Engineering');

// apply: array of arguments (A = Array)
logInfo.apply(employee, ['Developer', 'Engineering']);

// Using spread with call (modern preference)
const args = ['Manager', 'Sales'];
logInfo.call(employee, ...args);

// =====================================================
// 5. THE BIND() METHOD
// =====================================================

console.log('\n--- The bind() Method ---');

const user2 = {
  name: 'Bob',
  greet() {
    console.log('Hello, ' + this.name);
  },
};

// Without bind - context lost
const lostGreet = user2.greet;
// lostGreet();  // undefined

// With bind - context preserved
const boundGreet = user2.greet.bind(user2);
boundGreet(); // "Hello, Bob"

// bind vs call: bind returns function, call invokes immediately
console.log('\nbind returns a function:');
const fn = introduce.bind(person1);
console.log(typeof fn); // "function"
fn('Howdy', '!'); // "Howdy, I'm Alice!"

// =====================================================
// 6. BIND WITH PRESET ARGUMENTS
// =====================================================

console.log('\n--- bind() with Preset Arguments ---');

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

// Create specialized functions
const double = multiply.bind(null, 2);
const triple = multiply.bind(null, 3);
const quadruple = multiply.bind(null, 4);

console.log('double(5):', double(5)); // 10
console.log('triple(5):', triple(5)); // 15
console.log('quadruple(5):', quadruple(5)); // 20

// With this context
function greetFormal(greeting, title) {
  return `${greeting}, ${title} ${this.name}`;
}

const person = { name: 'Smith' };
const greetMr = greetFormal.bind(person, 'Good morning', 'Mr.');
const greetMs = greetFormal.bind(person, 'Good evening', 'Ms.');

console.log(greetMr()); // "Good morning, Mr. Smith"
console.log(greetMs()); // "Good evening, Ms. Smith"

// =====================================================
// 7. EVENT HANDLERS WITH BIND
// =====================================================

console.log('\n--- Event Handlers Pattern ---');

class Counter {
  constructor(name) {
    this.name = name;
    this.count = 0;

    // Bind method in constructor
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.count++;
    console.log(`${this.name}: ${this.count}`);
  }
}

const counter = new Counter('MyCounter');

// Can pass as callback without losing context
counter.increment(); // "MyCounter: 1"

// Simulating event callback
const callback = counter.increment;
callback(); // "MyCounter: 2" (works because we bound in constructor)

// =====================================================
// 8. METHOD BORROWING
// =====================================================

console.log('\n--- Method Borrowing ---');

// Array-like object (not a real array)
const arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
};

// Borrow Array methods
const realArray = Array.prototype.slice.call(arrayLike);
console.log('Converted to array:', realArray);

const joined = Array.prototype.join.call(arrayLike, '-');
console.log('Joined:', joined); // "a-b-c"

// Using map on array-like
const mapped = Array.prototype.map.call(arrayLike, (x) => x.toUpperCase());
console.log('Mapped:', mapped); // ["A", "B", "C"]

// Modern alternative
const modern = Array.from(arrayLike);
console.log('Array.from:', modern);

// =====================================================
// 9. BORROWING OBJECT METHODS
// =====================================================

console.log('\n--- Borrowing Object Methods ---');

const obj = { a: 1, b: 2 };

// Safe hasOwnProperty
const hasOwn = Object.prototype.hasOwnProperty;
console.log("Has 'a':", hasOwn.call(obj, 'a'));
console.log("Has 'c':", hasOwn.call(obj, 'c'));
console.log("Has 'toString':", hasOwn.call(obj, 'toString'));

// toString borrowing
const num = 255;
const hexString = Number.prototype.toString.call(num, 16);
console.log('255 in hex:', hexString); // "ff"

// =====================================================
// 10. CONSTRUCTOR BORROWING
// =====================================================

console.log('\n--- Constructor Borrowing ---');

function Animal(name) {
  this.name = name;
  this.isAlive = true;
}

function Dog(name, breed) {
  // Borrow Animal constructor
  Animal.call(this, name);
  this.breed = breed;
}

const myDog = new Dog('Rex', 'German Shepherd');
console.log('Dog:', myDog);
// { name: "Rex", isAlive: true, breed: "German Shepherd" }

// =====================================================
// 11. PARTIAL APPLICATION PATTERN
// =====================================================

console.log('\n--- Partial Application ---');

function log(level, timestamp, message) {
  console.log(`[${level}] ${timestamp}: ${message}`);
}

// Create specialized loggers
const info = log.bind(null, 'INFO');
const error = log.bind(null, 'ERROR');
const debug = log.bind(null, 'DEBUG');

const now = new Date().toISOString();
info(now, 'Application started');
error(now, 'Connection failed');
debug(now, 'Debug info here');

// Pre-fill more arguments
const currentLog = log.bind(null, 'INFO', new Date().toISOString());
currentLog('This is pre-timestamped');

// =====================================================
// 12. SETTIMEOUT/SETINTERVAL WITH BIND
// =====================================================

console.log('\n--- setTimeout with bind ---');

const timer = {
  name: 'MyTimer',
  count: 0,

  start() {
    console.log(`${this.name} starting...`);

    // Using bind
    setTimeout(
      function () {
        this.count++;
        console.log(`${this.name}: ${this.count}`);
      }.bind(this),
      100
    );
  },

  startArrow() {
    // Arrow function alternative
    setTimeout(() => {
      this.count++;
      console.log(`${this.name} (arrow): ${this.count}`);
    }, 200);
  },
};

timer.start();
timer.startArrow();

// =====================================================
// 13. BIND ONLY WORKS ONCE
// =====================================================

console.log('\n--- Bind Only Works Once ---');

function showThis() {
  console.log('this.name:', this?.name || 'undefined');
}

const bound1 = showThis.bind({ name: 'First' });
const bound2 = bound1.bind({ name: 'Second' }); // Ignored!

bound1(); // "First"
bound2(); // Still "First" - second bind is ignored

// =====================================================
// 14. USING CALL/APPLY WITH BUILT-INS
// =====================================================

console.log('\n--- Built-in Method Borrowing ---');

// Get type of value
function getType(value) {
  return Object.prototype.toString.call(value);
}

console.log('null:', getType(null)); // [object Null]
console.log('array:', getType([])); // [object Array]
console.log('object:', getType({})); // [object Object]
console.log(
  'function:',
  getType(() => {})
); // [object Function]
console.log('date:', getType(new Date())); // [object Date]

// =====================================================
// 15. PRACTICAL EXAMPLE: MIXIN
// =====================================================

console.log('\n--- Mixin Pattern ---');

const eventMixin = {
  on(event, handler) {
    this._events = this._events || {};
    (this._events[event] = this._events[event] || []).push(handler);
  },

  emit(event, ...args) {
    (this._events?.[event] || []).forEach((handler) => {
      handler.apply(this, args);
    });
  },
};

// Create an object and add event capabilities
const player = {
  name: 'Player1',
  score: 0,
};

Object.assign(player, eventMixin);

player.on('score', function (points) {
  this.score += points;
  console.log(`${this.name} scored! Total: ${this.score}`);
});

player.emit('score', 10);
player.emit('score', 25);

// =====================================================
// SUMMARY
// =====================================================

console.log('\n--- Summary ---');
console.log(`
call(context, arg1, arg2, ...)
  • Invoke immediately
  • Pass arguments individually
  • fn.call(obj, 1, 2, 3)

apply(context, [args])
  • Invoke immediately  
  • Pass arguments as array
  • fn.apply(obj, [1, 2, 3])

bind(context, arg1, arg2, ...)
  • Returns new function
  • Context permanently bound
  • Can preset arguments
  • const bound = fn.bind(obj, 1)

Use Cases:
  • Event handler binding
  • Method borrowing
  • Constructor borrowing
  • Partial application
  • setTimeout/setInterval
  • Callback preservation
`);
Examples - JavaScript Tutorial | DeepML