A Quick Look at the JavaScript apply() Method

rhieger

Robert Hieger

Posted on November 12, 2023

A Quick Look at the JavaScript apply() Method

JPE icon

This article was originally published on JavaScript in Plain English.


Woman Computer Programmer

                                             Photo by Kelly Sikkema on Unsplash

                                 Table of Contents

Introductory Remarks

A Brief Recap

Let's Get Started

What Needs to Change?

Now Let's Refactor the Code

     Step 1: Make the Needed Changes to index.html

     Step 2: Rename the CSS Style Sheet

     Step 3: Refactor the dog Objects to Use the apply() Method

     How Does Function.prototype.apply() Differ from Function.prototype.bind()?

     Recommended Reading for Study of call(), bind() and apply()

     Step 4: Refactor the Event Listeners for button1 through button3

What's Next?

Works Cited



In the third of three tutorials, we will examine the use of the apply() method.

Our main focus is refactoring the finished product from the previous tutorial so that it takes advantage of the apply() method, rather than the bind() method.

If you have not read the second tutorial in this series, you would do best to read through A Quick Look at the JavaScript bind() Method. Certainly, if you have not worked through the first tutorial in the series, you would do best to review A Quick Look at the JavaScript call() Method.

Our starting point will be the finished code from the second tutorial in this series.

A Brief Recap

Whereas the previous tutorial resulted in a simple demo application that demonstrated the use of the bind() method, in this tutorial we will refactor the same application to take advantage of the apply() method.

Figure 1 below shows the starting point for this tutorial—the opening screen of the application:



Opening Screen My Favorite Dogs

Fig. 1 My Favorite Dogs Opening Screen



When finished, the refactored application should produce exactly the same output as its two predecessors.

Let's Get Started

To get started, please download the tutorial starter code. Simply click the download icon found in the Github link as shown below in Figure 2:

Download Starter Code

Fig. 2 Github Starter Code Download Page



You can unzip the archive onto your local drive and you will see the following project files as shown in Figure 3 below:



Project Files

Fig. 3 Starter Code Project Files



What Needs to Change?

Two main parts of the application will require refactoring. The first is the index.html file, itself, changed mainly for semantic reasons and consistency with the refactored application's purpose. The second is the app.js file, and here the change is more extensive and substantive.

Code Listing 1 below shows the first block of code that will need to be refactored:

// Bound functions:
const dog1 = getDogInfo.info.bind(breed1);
const dog2 = getDogInfo.info.bind(breed2);
const dog3 = getDogInfo.info.bind(breed3);

// Dog Data Arrays
const dogBreed1 = dog1();
const dogBreed2 = dog2();
const dogBreed3 = dog3();
Enter fullscreen mode Exit fullscreen mode
Code Listing 1 Dog Objects and the dogBreed Arrays Resulting from their Invocation



In Code Listing 1, we see three dog objects. The values stored in these objects are derived from the getDogInfo object, on which the info() method is called and bound to the breed object, which, in turn, contains the properties of each favorite dog breed.

In the code that follows, dog1 through dog3 are called and return three arrays assigned to dogBreed1 through dogBreed3. This was the method employed in the last tutorial to assemble the data that populates the three rows of data on favorite dog breeds.

Code Listing 2 below shows the last block of JavaScript code with which we are concerned in this tutorial:

// Button Event Listners:
button1.addEventListener( 'click', () => {
  setRowCotent(
    breedOne,
    breedOrigin1,
    breedAvgLife1,
    dogBreed1
  );
});

button2.addEventListener( 'click', () => {
  setRowCotent(
    breedTwo,
    breedOrigin2,
    breedAvgLife2,
    dogBreed2
  );
});

button3.addEventListener( 'click', () => {
  setRowCotent(
    breedThree,
    breedOrigin3,
    breedAvgLife3,
    dogBreed3
  );
});
Enter fullscreen mode Exit fullscreen mode

As in the second tutorial of this series, we will need to refactor the event listeners for button1 through button3. button4 remains uniform in all three tutorials in this series.

Now Let's Refactor the Code

We can now refactor the code to take advantage of the apply() method. Once you have unzipped the starter code archive, follow the steps below:

Step 1: Make the Needed Changes to index.html

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>bind() Method Demo</title>
  <script src="./js/app.js" defer></script>
  <link rel="stylesheet" href="./css/bind-demo.css">
</head>
Enter fullscreen mode Exit fullscreen mode
Code Listing 3 Section of index.html

As in the previous tutorial, we are concerned only with two lines in Code Listing 3 above.

Make the following changes to index.html:

1) Change the line with the <title> tag so it reads as follows:

<title>apply() Method Demo</title>
Enter fullscreen mode Exit fullscreen mode

2) Change the line with the <link> tag so it reads as follows:

<link rel="stylesheet" href="./css/apply-demo.css">
Enter fullscreen mode Exit fullscreen mode



Step 2: Rename the CSS Style Sheet

If you refer to Figure 3 above, you will see that the CSS style sheet is named bind-demo.css. This name does not match the name of the style sheet referenced in our revised <link> tag.

Rename bind-demo.css to apply-demo.css.

If you open index.html in your browser, everything will display correctly. In fact, if you click the buttons of the app, they will function exactly as they did in the previous tutorial. This is due to the original code in our starter files for the project.

Now we can explore in depth how to refactor the application logic found in app.js so that it takes advantage of the apply method instead of the bind() method.



Refactor the dog Objects to Use the apply() Method

// Get info for each breed object:
const getDogInfo = {
  info: function() {
    return [
      this.breed,
      this.origin,
      this.lifeSpan
    ];
  }
};

// Bound functions:
const dog1 = getDogInfo.info.bind(breed1);
const dog2 = getDogInfo.info.bind(breed2);
const dog3 = getDogInfo.info.bind(breed3);

// Dog Data Arrays:
const dogBreed1 = dog1();
const dogBreed2 = dog2();
const dogBreed3 = dog3();
Enter fullscreen mode Exit fullscreen mode
Code Listing 4 The getDogInfo Object and the Derived dog Objects

Let's review the code above in Code Listing 4 to better understand what we will be changing in the refactored code.

In our starter code, the info() method is called on the getDogInfo object, and the bind() method is chained to the info() method.

Three sequentially numbered breed objects are passed to the bind() method and the results are store to three sequentially numbered dog objects.

Finally, as shown in the finished code from the last tutorial, each dog object is called and its results stored in the sequentially numbered dogBreed objects, the contents of which are arrays containing the dog breed information to be injected dynamically in the table rows shown above in Figure 1 at the beginning of this article.

Our objective in refactoring the code seen in Code Listing 4 is to replace the bind() method called on the dog objects with the apply() method.

How Does Function.prototype.apply() Differ from Function.prototype.bind()?

The bind() method returns a new function that, upon being called, has its reference to which the keyword this is set. It may also have a set of optional arguments that must precede any that are passed at the time the new returned function is called.

The apply() method, in contrast, calls a function whose reference for the this keyword has been set, and then an array (or array-like object) containing any arguments required in the function.

Now let's see how to refactor this code in Code Listing 4 above to take advantage of the apply() method.

Make the following changes to the code:

In place of the dog object declarations above in Code Listing 4, type in the code listed below in Code Listing 5:

// Bound functions:
const dog1 = getDogInfo.info.apply(
  breed1, [
    breed1[0],
    breed1[1],
    breed1[2]
  ]
);

const dog2 = getDogInfo.info.apply(
  breed2, [
    breed2[0],
    breed2[1],
    breed2[2]
  ]
);

const dog3 = getDogInfo.info.apply(
  breed3, [
    breed3[0],
    breed3[1],
    breed3[2]
  ]
);
Enter fullscreen mode Exit fullscreen mode
Code Listing 5 Refactoring of 3 dog Objects



There are two notable differences in the code for the three dog objects above. The first is that the bind() method is replaced by the apply() method. The remaining difference in our refactored code has bigger implications, as we will soon see.

The first argument to the apply() method in the objects defined above is a reference object to which the this keyword refers. Then there is an array of arguments, as discussed above. In this case, the arguments are the elements of the array that contains the content to go into the three table cells of each row containing dog breed information.

The second rather notable difference is that when using the apply() method, because no new object is returned, as is the case when using bind(), there is no longer any need to create dogBreed objects to model the data for the table rows, as shown earlier in Code Listing 4.

This definitely makes for more compact code, cutting out a full three lines of code. As we directly access the values stored in the dog objects created with the getDogInfo.info() method call, also shown above in Code Listing 5, we are able to access the indices in the dog arrays directly, eliminating an interim step.

Does this mean that the bind() method is inefficient or undesirable? In this case, the answer is bigger than the question. The concepts involved are fairly advanced, but several articles have been published that explore the question of optimum use cases for the three methods—call(), bind() and apply()—explored in this tutorial series.

Recommended Reading for Study of call(), bind() and apply()

 

One fairly comprehensive article that is worth a read is How AND When to use bind, call, and apply in JavaScript. This article gives a broad overview of the relative utility and good use cases for each method. Though the information in this article is definitely fairly advanced, it is worthwhile to spend some time with the ideas expressed there as they begin to clarify how best to use each of these methods.



Step 4: Refactor the Event Listeners for button1 through button3

As one final step in our refactoring, one subtle change must be made to the event listeners for button1 through button3, as shown earlier in Code Listing 2.

Referring back to Code Listing 2, you will note that the last argument in the setRowContent() callback function reads dogBreed1, dogBreed2 and dogBreed3. These arguments hearken back to our previous iteration of the application, which used the bind() method.

The bind() method, as you will recall, set the value of the this keyword to the specified object—in our case breed1, breed2 and breed3. However, in the process, a new object is returned. In order to access the properties in that object the info() method was called and the apply() method was chained to it. The result was stored in brand new arrays stored to the constants dogBreed1-dogBreed3.

Using the apply() method, we can cut out this "middleman" step, by allowing access directly to the properties within the object reference set for the this keyword. We may therefore access properties from dog1, dog2 and dog3.

For convenience, Code Listing 6 below shows the relevant code to be refactored:

// Button Event Listeners:
button1.addEventListener('click', () => {
  showRowContent(
    breedOne,
    breedOrigin1,
    breedAvgLife1,
    dogBreed1
  );
});

button2.addEventListener('click', () => {
  showRowContent(
    breedTwo,
    breedOrigin2,
    breedAvgLife2,
    dogBreed2
  );
});

button3.addEventListener('click', () => {
  showRowContent(
    breedThree,
    breedOrigin3,
    breedAvgLife3,
    dogBreed3
 );
});
Enter fullscreen mode Exit fullscreen mode
Code Listing 6 Event Listeners to be Refactored



Refactoring the above code is fairly trivial—merely a change of the last argument in each of the setRowContent() methods. But as discussed earlier, the implications of this change are significant because the extra step of saving the values to be placed in each row to constants is eliminated.

Now we do the refactoring of the code above, making the minor changes described above. Make sure that your code matches that shown in Code Listing 7:

// Button Event Listeners:
button1.addEventListener('click', () => {
  showRowContent(
    breedOne,
    breedOrigin1,
    breedAvgLife1,
    dog1
  );
});

button2.addEventListener('click', () => {
  showRowContent(
    breedTwo,
    breedOrigin2,
    breedAvgLife2,
    dog2
  );
});

button3.addEventListener('click', () => {
  showRowContent(
    breedThree,
    breedOrigin3,
    breedAvgLife3,
    dog3
  );
});
Enter fullscreen mode Exit fullscreen mode
Code Listing 7 Refactored Event Listeners for button1 through button3



Now that these changes have been implemented, you can see the results by pointing your browser at the index.html file in your application folder, or by going to the page live on codepen.io.

What's Next?

At the end of the first tutorial, I mentioned that the little application we created is simplistic and does not take into account user input. As a result, there is the rather artless paradigm of a button bar with four buttons, three of which fill in details about three pre-determined dog breeds and one that resets the table to its initial state. I indicated that I would be refactoring the current application to be a far more robust, dynamic and user interactive Single Page Application (SPA).

The current application barely qualifies as an application with dynamic generation of data. I intend to build a new version soon, which will include a modal dialogue in which a user can input the dog breed information of choice. The breeds will therefore not be pre-determined as in this version of the application.

The implications of these changes are profound from a user interface perspective and also a data validation perspective, as it cannot be predicted ahead of time what a user will enter, and whether the data entered is valid.

Please stay tuned for my next tutorial—My Favorite Dogs—A Simple Single Page Application (SPA).


Works Citied

Hieger, Robert. "A Quick Look at the bind() Method:
     Understand the bind() Method in JavaScript." dev.to,
     DEV Community, 22 Aug 2021, https://dev.to/rhieger/a-quick-look-at-the-javascript-bind-method-understand-the-bind-method-in-javascript-7hh

---. "A Quick Look at the JavaScript call() Method." dev.to,
     DEV Community, 5 May 2021, https://dev.to/rhieger/a-quick-look-at-the-javascript-call-method-5ghc.

"How and When to Use bind, call, and apply in JavaScript."
     Eigenx, Eigen X, 2 Aug. 2018 https://www.eigenx.com/blog/https/mediumcom/eigen-x/how-and-when-to-use-bind-call-and-apply-in-javascript-77b6f42898fb.

💖 💪 🙅 🚩
rhieger
Robert Hieger

Posted on November 12, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related