Skip to main content

Concepts

Closure

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function

-- mdn_web_docs

Using closure has below benefits:

  • Data encapsulation: It can be used to create private variables and functions that can't be accessed from outside the closure. This is useful for hiding implementation details and maintaining state in an encapsulated way.
  • Functional programming: Closures enable creating functions that can contain context and be passed around for invoke later.
  • Event handlers/callbacks: Closures are often used in event handlers and callbacks to maintain state or access variables that were in scope when the handler or callback was defined.
  • Module patterns: Closures enable the creation of modules with private and public parts.
Readings

call, apply and bind

Usage

Both call and apply are used to invoke functions with a specific this context and arguments. The difference lies in how they accept arguments, where:

  • call(thisArg, arg1, arg2, ...): Takes arguments individually.
  • apply(thisArg, [argsArray]): Takes arguments as an array.

If the function is not in strict mode, thisArg will be replaced with the global object if null and undefined is provided, and primitive values will be converted to objects; otherwise, thisArg will remian whatever is passed in.

A common use case to use call and apply is to invoke functions on different objects by explicitly assign the this context. For instance,

const person = {
name: 'John',
greet() {
console.log(`Hello, this is ${this.name}`);
},
};

const anotherPerson = { name: 'Alice' };
const greetFunc = person.greet;

person.greet.call(anotherPerson); // Hello, my name is Alice
person.greet.apply(anotherPerson); // Hello, my name is Alice
greetFunc.call(anotherPerson); // Hello, my name is Alice
greetFunc.call(anotherPerson); // Hello, my name is Alice


function greet() {
console.log(`Hello, my name is ${this.name}`);
}

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

greet.call(person1); // Hello, my name is John
greet.call(person2); // Hello, my name is Alice

Whereas bind creates a new function with a specific this context and, optionally, preset arguments. bind is most useful for preserving the value of this in methods of classes that you want to pass into other functions

const person = {
name: 'John',
greet() {
console.log(`Hello, this is ${this.name}`);
},
};

const anotherPerson = { name: 'Alice' };
const greetFunc = person.greet;

greetFunc.bind(anotherPerson)(); // // Hello, my name is Alice

bind is often used to preserve the this context when a function is passed as callback.

class Person {
constructor(name) {
this.name = name;
}

// this context lost when execute context is lost
greet() {
console.log(`Hello, my name is ${this.name}`);
}

// Arrow functions have the this value bound to its lexical context.
hi = () => {
console.log(`Hi, this is ${this.name}`);
}
};

const john = new Person('John Doe');

setTimeout(john.greet, 1000); // Hello, my name is undefined
setTimeout(john.greet.bind(john), 2000); // Hello, my name is John Doe
setTimeout(john.hi, 2000); // Hello, my name is John Doe

bind can also be used to create a new function with some arguments pre-set. This is known as partial application or currying.

function getName(firstName='', lastName='') {
return firstName + ' ' + lastName;
}

const printName = getName.bind(null, 'John');
console.log(printName()); // John
console.log(printName('Doe')); // John Doe

Interchangeability

This three functions can be treated as sibling functions and can be implemented using one another.

/**
* Custom call
*/
Function.prototype.customCall = function(thisArgs, ...args){
return this.bind(thisArg)(...argArray);
}

// or

Function.prototype.customCall = function(thisArgs, ...args){
return this.bind(thisArg, ...argArray)();
}
/**
* Custom apply
*/
Function.prototype.customApply = function (thisArg, args = []) {
return this.bind(thisArg)(...args);
};

// or

Function.prototype.customApply = function (thisArg, args = []) {
return this.bind(thisArg)(...args);
};

// or

Function.prototype.customApply = function (thisArg, args = []) {
return this.call(thisArg, ...argArray);
};
/**
* Custom Bind
*/
Function.prototype.customBind = function (thisArg, ...argArray) {
const originalMethod = this;
return function (...args) {
return originalMethod.apply(thisArg, [...argArray, ...args]);
};
};

// or

Function.prototype.customBind = function (thisArg, ...argArray) {
const originalMethod = this;
return function (...args) {
return originalMethod.call(thisArg, ...argArray, ...args);
};
};
Readings