jQuery Document Ready…use it wisely!

jQuery has done an excellent job with the document .ready() function which allows us to accurately know when the DOM has been loaded so that we can begin processing our JavaScript code. Prior to this we had to rely on the windows onLoad event which tends to occur much later in the load cycle.

Usually, one ready handler per page or view is used as the document ready handler will parse/execute the code once the DOM has loaded. Having this handler process code not relevant to the document at hand would be wasting resources.

Unfortunately I have noticed that this facility is used freely and loosely, even when sometimes it is not required, under the guise of “just to be on the safe side”. Not using this function properly has an impact on the performance of the application.

Consider a very simple example. We have two JS files test1.js and test2.js which are respectively included in test1.php and test2.php. These files are shown below:

test1.php

<html>
<head>
   <title>Test 1</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test1.js"></script>
</head>
<body>

<div id="testDiv1">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

and the JS include file test1.js:

(function($) {
   $(document).ready(function(event) {
      console.log('test1.js - Number of tr elements: ' + $('#testDiv1 table tr').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

When run, the php file prints out a table structure with 100 rows. When the DOM is loaded(the table structure in our case) the ready event fires and prints out a message on the console:
test1.js – Number of tr elements: 100

test2.php and test2.js are similar, except this one prints out the number of ‘td’ elements instead of tr.

test2.php

<html>
<head>
    <title>Test 2</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test2.js"></script>
</head>
<body>

<div id="testDiv2">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

and the JS include file test2.js:

(function($) {
   $(document).ready(function(event) {
      console.log('test2.js - Number of td elements: ' + $('#testDiv2 table td').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

When run the console prints out:
test2.js – Number of td elements: 100

As long as each JS file with the ready function is included in its respective host file or view everything works as it should without any overlap or conflict. But life is not so simple.

To reduce page load times it is customary to join many files into one and then minify the resulting file. This takes away the performance degradation due to latency issues as only one file is loaded compared to many.

By the same token it is also advisable that JS code specific to a view or a page be loaded per page while common code be bundled together and minified. This leads to a highly efficient load which does not tax the resources and also keeps the latency issues to a minimum.

Recently I came across an open source project based on the MVC pattern which joined 55 JS files, with each file having its document ready function. This bundle included common and specific files. Let me illustrate to you how much of a performance impact this can make. Let’s join test1.js and test2.js into one file and call it test.js. I have not minified this file for the sake of clarity.

test.js

(function($) {
   $(document).ready(function(event) {
      console.log('test1.js - Number of tr elements: ' + $('#testDiv1 table tr').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

(function($) {
   $(document).ready(function(event) {
      console.log('test2.js - Number of td elements: ' + $('#testDiv2 table td').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

Also let’s modify test1.php to include this file:
test1.php

<html>
<head>
   <title>Test with joined JS file</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test.js"></script>
</head>
<body>

<div id="testDiv1">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

If we now run this we get the following on the console:
Console [5]=
test1.js – Number of tr elements: 100

Console [6]=
test2.js – Number of td elements: 0

Notice that there are two document ready handlers in test.js, and both ran. This is because when the DOM loads they cannot distinguish a relevant file from an irrelevant one. The first message is valid as it relates to test1.php but the second is not, as it relates to test2.php. This means the effort put in by the JS engine for the second handler was a waste.

Usually the document ready handler does not contain a simple log message, but a slew of event bindings and other JS code. Some of the files in the aforementioned project had close to a dozen event bindings. Even if we were to assume an average of five event bindings per file, that’s 275 bindings. Take into account event bubbling, live bindings and we are talking serious performance problems.

In such circumstances we cannot rescue everything but we can inject a little saving grace.

Here is the same test1.php and test.js with changes, which I will explain in a minute:

test1.php

<html>
<head>
   <title>Test with joined JS file</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test.js"></script>
</head>
<!-- Note the new title attribute -->
<body title="test1">

<div id="testDiv1">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

Notice that in the body tag I have added a title attribute.

test.js

(function($) {
   $(document).ready(function(event) {
      //check if for this handler ***NEW****
      var title = $('body').attr('title');
      if(title !== 'test1'){
         return false;
      };
      console.log('test1.js - Number of tr elements: ' + $('#testDiv1 table tr').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

(function($) {
   $(document).ready(function(event) {
      //check if for this handler ***NEW***
      var title = $('body').attr('title');
      if(title !== 'test2'){
         return false;
      };
      console.log('test2.js - Number of td elements: ' + $('#testDiv2 table td').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

If you run this now you will get the following in the console:
test1.js – Number of tr elements: 100

Let me explain why.

In test.php I have added a title attribute which identifies this file

In the JS file I have added the following code in the document ready handler:
//check if for this handler
var title = $(‘body’).attr(‘title’);
if(title !== ‘test1’){
return false;
};

It only continues to processes the handler code if the title attribute matches “test1”. So the handler connected with test2 never continues which saves resources.

The crux of the idea is to make processing relevant to the document at hand. This is only one way of doing it. Many other ways come to mind and I leave them to your imagination.

I hope this helps you and I look forward to your feedback.

Advertisements

3 thoughts on “jQuery Document Ready…use it wisely!

  1. Usually I do not read article on blogs, but I wish to say that this write-up very forced me to check out and do so! Your writing taste has been surprised me. Thanks, quite great post.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s