skip to Main Content

This code sample from "Node.js Design Patterns – Third edition: Design and implement production-grade Node.js applications using"

function patchCalculator (calculator) {
  // new method
  calculator.add = function () {
    const addend2 = calculator.getValue()
    const addend1 = calculator.getValue()
    const result = addend1 + addend2
    calculator.putValue(result)
    return result
  }
  // modified method
  const divideOrig = calculator.divide
  calculator.divide = () => {
    // additional validation logic
    const divisor = calculator.peekValue()
    if (divisor === 0) {
      throw Error('Division by 0')
    }
    // if valid delegates to the subject
    return divideOrig.apply(calculator)
  }
  return calculator
}
const calculator = new StackCalculator()
const enhancedCalculator = patchCalculator(calculator)
//

and its a section talking about Object augmentation when discussing Object decoration.

I am just confused on what apply is doing. Here is my guess, the patchCalculator is applying that new method, to the original object calculator and enhancedCalculator are having the same implementation? is this correct?

thanks in advance for the help, i am not sure why apply confuses me so much and the different usages of it.

2

Answers


  1. Yes I think you are correct. MDN page for .apply

    In this case, that the .apply applies the divideOrig method to the calculator object, but just in a less redundant way than the .add method is shown, and also can be a modification of the original .divide function, and not have to be completely rewritten (aka, redefined from scratch, as .add is here).

    Login or Signup to reply.
  2. Let me start of that the code:

    const calculator = new StackCalculator()
    const enhancedCalculator = patchCalculator(calculator)
    

    Is very deceptive to start with. Currently it looks like calculator and enhancedCalculator are two different objects. However patchCalculator() does not create a new object, but returns the passed argument (with mutated properties).

    This means that the above could also be written as:

    const calculator = new StackCalculator()
    patchCalculator(calculator)
    

    This will "update" the calculator variable to now be the patched variant. With this deception out of the way, lets get to the answer.


    apply() is used to invoke a function with a manually provided this argument.

    Under normal circumstances this is automatically set to the receiver of the method call. Take for example person.greet(), in this example person is the receiver of the greet() method invocation and is used as the this argument inside greet().

    The code might look like this:

    function greet() {
      return `Hello my name is ${this.firstName} ${this.lastName} and I'm ${this.age} years old.`;
    }
    

    Currently the above is a function and, when called by itself greet() this will be globalThis or undefined in strict mode. To use this method it has to be coupled to an object with firstName, lastName and age properties.

    Say the person object is defined like this:

    const person = { firstName: "John", lastName: "Doe", age: "42" };
    

    Now we can attach the greet function as one of the person properties and invoke it.

    person.greet = greet;
    person.greet();
    //=> "Hello my name is John Doe and I'm 42 years old."
    
    function greet() {
      return `Hello my name is ${this.firstName} ${this.lastName} and I'm ${this.age} years old.`;
    }
    
    const person = { firstName: "John", lastName: "Doe", age: "42" };
    
    person.greet = greet;
    console.log(person.greet());

    Now say we either can’t, or don’t want to mutate the person object. In this scenario we can use apply() or call() to call the function. Providing the this function argument manually, rather than it being assigned based on the receiver.

    greet.apply(person);
    //=> "Hello my name is John Doe and I'm 42 years old."
    greet.call(person);
    //=> "Hello my name is John Doe and I'm 42 years old."
    
    function greet() {
      return `Hello my name is ${this.firstName} ${this.lastName} and I'm ${this.age} years old.`;
    }
    
    const person = { firstName: "John", lastName: "Doe", age: "42" };
    
    console.log(greet.apply(person));
    console.log(greet.call(person));

    Relating this to your example data we can see that:

    const divideOrig = calculator.divide
    

    Takes the divide method, and assigns it to the divideOrig variable.

    We then override the calculator.divide property with our new method. Within this new variant of divide we call divideOrig.apply(calculator) to get the original result back. This calls the original calculator.divide() (which is now assigned to divideOrig), and manually sets calculator as the this argument.

    If we where to call divideOrig() by itself it doesn’t have a receiver (there is no object in front of it). Meaning that this would be set to globalThis or undefined like discussed earlier.

    My guess is that the original calculator.divide() depends on the properties/state of calculator. Therefore we need to make sure that calculator is set as the this argument.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search