Modern JavaScript Tutorial Part 1.6 Advanced Learning of Functions Fourth

Table of Contents

1. Function Binding

When an object method is passed to a different context or used as a callback, the information about this is lost. For instance, the setTimeout function causes this loss of this information.

let me = {
  firstName: "Kim Seong-hyun",
  greeting() {
    console.log(`Hello. My name is ${this.firstName}.`);
  },
};

me.greeting(); // Hello. My name is Kim Seong-hyun.
setTimeout(me.greeting, 1000); // Hello. My name is undefined.

This occurs because only the function detached from the object context is passed to setTimeout.

In a browser environment, the setTimeout method will reference the window object for this.

let me = {
  firstName: "Kim Seong-hyun",
  greeting() {
    console.log(`Hello. My name is ${this.firstName}.`);
  },
};
window.firstName = "Unnamed";

me.greeting(); // Hello. My name is Kim Seong-hyun.
setTimeout(me.greeting, 1000); // Hello. My name is Unnamed.
// In the browser environment, this refers to window, hence window.firstName is displayed.

So how can we maintain the context when passing an object method?

1.1. Wrapper Function

By creating a wrapper function like this, the call to me.greeting occurs as a method of the me object, rather than being called as a callback, ensuring it works properly.

let me = {
  firstName: "Kim Seong-hyun",
  greeting() {
    console.log(`Hello. My name is ${this.firstName}.`);
  },
};

setTimeout(function () {
  me.greeting();
}, 1000);

However, if me.greeting is changed before the setTimeout callback is executed, an issue arises.

let me = {
  firstName: "Kim Seong-hyun",
  greeting() {
    console.log(`Hello. My name is ${this.firstName}.`);
  },
};

setTimeout(function () {
  me.greeting();
}, 1000);
me.greeting = () => {
  console.log("Modified function");
};
// After 1 second, 'Modified function' is displayed.

In such cases, the bind method can be used instead.

1.2. Bind

func.bind(context) returns a new function that fixes this to the specified context when func is called. Therefore, we can create a new function that fixes this of me.greeting to me and pass it to setTimeout.

let me = {
  firstName: "Kim Seong-hyun",
  greeting() {
    console.log(`Hello. My name is ${this.firstName}.`);
  },
};

let myGreeting = me.greeting.bind(me);
setTimeout(myGreeting, 1000);

Using bind fixes only this while preserving the function arguments.

If an object has multiple methods and you want to bind them all, you can use a loop.

// Binding methods using a loop
for(let key in obj){
  if(typeof obj[key] === 'function'){
    obj[key] = obj[key].bind(obj);
  }
}

1.3. Argument Binding

The bind method can also fix function arguments in addition to this. You can pass arguments to the bind method like this.

function f(a, b) {
  console.log(a + b);
}

let addTwo = f.bind(null, 2);
console.log(addTwo(1)); // 3

This approach is called a partial function, which creates a new function by fixing some parameters of an existing function. It can be used to generate several variations based on a comprehensive function.

However, using the bind method requires a default object for this, which can be cumbersome. Therefore, we can create a function that handles this automatically while fixing arguments.

// argsBound represents the fixed arguments
function partial(func, ...argsBound) {
  return function (...args) {
    // Use call to utilize this same as an object method
    return func.call(this, ...argsBound, ...args);
  };
}

Using this function allows the this of the pre-fixed function to be bound to the partially fixed function.

1.4. Characteristics of Bind

The special object called a bound function returned by bind remembers the context at the time of function creation and only the arguments provided by bind. Therefore, calling bind again on an already bound function will be ignored.

function f() {
  console.log(this.name);
}

let g = f.bind({ name: "John" }).bind({ name: "Kim Seong-hyun" });
g();
// John

Additionally, since a new function object is created and returned when using bind, existing properties of the original function are not transferred to the bound function.

function f() {
  console.log(this.name);
}
f.test = 1;

let g = f.bind({ name: "John" });
console.log(f.test); // 1
console.log(g.test); // undefined

2. Arrow Functions Revisit

In JavaScript, when using methods like forEach, it is common to create and pass functions. Using arrow functions in this scenario preserves the current context.

Arrow functions do not have their own this. When this is used in an arrow function, it takes the value from the enclosing non-arrow function. Therefore, using arrow functions allows access to this of the enclosing context without creating a separate this.

For example, consider an object that holds study members and has a function to print them one by one. If we pass a function created using function to forEach, the this of that function will refer to window (in a browser environment).

let study = {
  goal: "study JS",
  people: ["John", "Jane", "Jack", "Jill"],

  printPeople: function () {
    this.people.forEach(function (person) {
      // In this case, `this` will point to window, resulting in unexpected output
      console.log(`${this.goal}: ${person}`);
    });
  },
};

study.printPeople();

To make it work correctly, we must use an arrow function.

let study = {
  goal: "study JS",
  people: ["John", "Jane", "Jack", "Jill"],

  printPeople: function () {
    this.people.forEach((person) => {
      // Here, `this` points to the object that called the enclosing function printPeople
      console.log(`${this.goal}: ${person}`);
    });
  },
};

study.printPeople();
/* study JS: John
study JS: Jane
study JS: Jack
study JS: Jill */

Since arrow functions do not have this, they cannot be used as constructors. Additionally, bind operates differently; bind creates a new function that fixes this to a specified context and can set this to a completely different object.

However, arrow functions do not specifically bind this; they simply use the this from their enclosing lexical environment.