Call, Bind, Apply

Understanding how to manipulate function context (this)

What are Call, Bind, and Apply?

These are methods that allow you to explicitly set the 'this' context of a function. They're useful for borrowing methods and controlling function execution context.

Try Call, Bind, Apply

Code Editor
Console Output
Click "Run" to execute your code...

Example 1: call() Method

// call() invokes function with specified 'this' and arguments
              const person1 = {
                name: 'John',
                age: 30
              };

              const person2 = {
                name: 'Jane',
                age: 25
              };

              function greet(greeting, punctuation) {
                return `${greeting}, I'm ${this.name} and I'm ${this.age} years old${punctuation}`;
              }

              // Using call()
              console.log(greet.call(person1, 'Hello', '!'));
              // Hello, I'm John and I'm 30 years old!

              console.log(greet.call(person2, 'Hi', '.'));
              // Hi, I'm Jane and I'm 25 years old.

              // Borrowing methods
              const obj1 = {
                name: 'Object 1',
                display: function() {
                  console.log(`Name: ${this.name}`);
                }
              };

              const obj2 = {
                name: 'Object 2'
              };

              // obj2 doesn't have display method, but can borrow it
              obj1.display.call(obj2); // Name: Object 2

              // Practical example: Array-like objects
              function printArguments() {
                // arguments is array-like but not an array
                Array.prototype.forEach.call(arguments, (arg, index) => {
                  console.log(`Arg ${index}: ${arg}`);
                });
              }

              printArguments('a', 'b', 'c');
              // Arg 0: a
              // Arg 1: b
              // Arg 2: c

Example 2: apply() Method

// apply() is like call(), but takes arguments as an array
              const person = {
                name: 'Alice',
                age: 28
              };

              function introduce(greeting, hobby, location) {
                return `${greeting}! I'm ${this.name}, ${this.age}. I love ${hobby} in ${location}.`;
              }

              // Using apply() with array of arguments
              const args = ['Hi', 'coding', 'New York'];
              console.log(introduce.apply(person, args));
              // Hi! I'm Alice, 28. I love coding in New York.

              // Compare with call()
              console.log(introduce.call(person, 'Hello', 'reading', 'Boston'));

              // Practical use: Math.max with array
              const numbers = [5, 6, 2, 3, 7, 9, 1];

              // Math.max doesn't accept arrays
              // console.log(Math.max(numbers)); // NaN

              // Use apply to spread array as arguments
              console.log(Math.max.apply(null, numbers)); // 9
              console.log(Math.min.apply(null, numbers)); // 1

              // Modern alternative: spread operator
              console.log(Math.max(...numbers)); // 9

              // Concatenating arrays
              const arr1 = [1, 2, 3];
              const arr2 = [4, 5, 6];

              // Using apply
              Array.prototype.push.apply(arr1, arr2);
              console.log(arr1); // [1, 2, 3, 4, 5, 6]

              // Modern alternative
              const arr3 = [7, 8, 9];
              const arr4 = [10, 11, 12];
              arr3.push(...arr4);
              console.log(arr3); // [7, 8, 9, 10, 11, 12]

Example 3: bind() Method

// bind() creates a new function with fixed 'this' value
              const person = {
                name: 'Bob',
                age: 35,
                greet: function() {
                  console.log(`Hello, I'm ${this.name}`);
                }
              };

              // Regular method call
              person.greet(); // Hello, I'm Bob

              // Problem: losing 'this' context
              const greetFunction = person.greet;
              // greetFunction(); // TypeError or undefined (this is not person)

              // Solution: bind()
              const boundGreet = person.greet.bind(person);
              boundGreet(); // Hello, I'm Bob

              // Partial application with bind()
              function multiply(a, b) {
                return a * b;
              }

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

              const triple = multiply.bind(null, 3);
              console.log(triple(5));  // 15

              // Event listeners example
              const button = {
                text: 'Click me',
                handleClick: function() {
                  console.log(`Button text: ${this.text}`);
                }
              };

              // In browser:
              // document.getElementById('btn').addEventListener('click', button.handleClick);
              // Would lose 'this' context

              // Fix with bind:
              // document.getElementById('btn').addEventListener('click', button.handleClick.bind(button));

              // React class component example pattern
              class Counter {
                constructor() {
                  this.count = 0;
                  // Bind in constructor
                  this.increment = this.increment.bind(this);
                }
                
                increment() {
                  this.count++;
                  console.log(`Count: ${this.count}`);
                }
              }

              const counter = new Counter();
              const inc = counter.increment;
              inc(); // Count: 1 (works because of bind!)

Example 4: Comparing call, apply, and bind

const obj = { value: 42 };

              function showValue(prefix, suffix) {
                return `${prefix} ${this.value} ${suffix}`;
              }

              // call() - immediate invocation, individual arguments
              console.log(showValue.call(obj, 'Value:', '!'));
              // Value: 42 !

              // apply() - immediate invocation, array of arguments
              console.log(showValue.apply(obj, ['Value:', '!']));
              // Value: 42 !

              // bind() - returns new function, can be called later
              const boundShow = showValue.bind(obj, 'Value:');
              console.log(boundShow('!'));
              // Value: 42 !

              // Key differences:
              // - call & apply: invoke immediately
              // - bind: returns new function
              // - call: arguments listed individually
              // - apply: arguments as array
              // - bind: can preset arguments (partial application)

              // Chaining example
              function log(level, message) {
                console.log(`[${level}] ${this.name}: ${message}`);
              }

              const logger = { name: 'MyLogger' };

              // Create specialized loggers
              const info = log.bind(logger, 'INFO');
              const error = log.bind(logger, 'ERROR');
              const warn = log.bind(logger, 'WARN');

              info('Application started');    // [INFO] MyLogger: Application started
              error('Something went wrong');  // [ERROR] MyLogger: Something went wrong
              warn('This is a warning');      // [WARN] MyLogger: This is a warning

Example 5: Practical Use Cases

// Use Case 1: Function borrowing
              const person1 = {
                firstName: 'John',
                lastName: 'Doe',
                getFullName: function() {
                  return `${this.firstName} ${this.lastName}`;
                }
              };

              const person2 = {
                firstName: 'Jane',
                lastName: 'Smith'
              };

              console.log(person1.getFullName.call(person2)); // Jane Smith

              // Use Case 2: Converting array-like to array
              function toArray() {
                return Array.prototype.slice.call(arguments);
              }

              const arr = toArray(1, 2, 3, 4, 5);
              console.log(arr); // [1, 2, 3, 4, 5]
              console.log(Array.isArray(arr)); // true

              // Modern alternative:
              function toArrayModern() {
                return Array.from(arguments);
                // or [...arguments]
              }

              // Use Case 3: Curry functions
              function add(a, b, c) {
                return a + b + c;
              }

              const add5 = add.bind(null, 5);
              const add5And10 = add5.bind(null, 10);

              console.log(add5And10(15));     // 30 (5 + 10 + 15)
              console.log(add5(10, 15));      // 30 (5 + 10 + 15)

              // Use Case 4: Timing and debouncing
              const processor = {
                name: 'DataProcessor',
                process: function(data) {
                  console.log(`${this.name} processing: ${data}`);
                }
              };

              // Without bind, setTimeout loses context
              // setTimeout(processor.process, 1000); // Error

              // With bind
              setTimeout(processor.process.bind(processor, 'important data'), 1000);
              // DataProcessor processing: important data

              // Use Case 5: Polyfill for bind (understanding how it works)
              if (!Function.prototype.customBind) {
                Function.prototype.customBind = function(context, ...args) {
                  const fn = this;
                  return function(...newArgs) {
                    return fn.apply(context, [...args, ...newArgs]);
                  };
                };
              }

              function test(a, b) {
                return `${this.value} - ${a} - ${b}`;
              }

              const boundTest = test.customBind({ value: 'Test' }, 'arg1');
              console.log(boundTest('arg2')); // Test - arg1 - arg2

Key Points

  • call() and apply() invoke function immediately with specified 'this'
  • bind() returns a new function with fixed 'this' value
  • call() takes arguments individually: func.call(thisArg, arg1, arg2)
  • apply() takes arguments as array: func.apply(thisArg, [arg1, arg2])
  • bind() can preset arguments (partial application)
  • Useful for method borrowing and preserving context
  • Arrow functions cannot be bound (they lexically bind 'this')