Web applications are getting more and more complex. The user interface of a modern web application can be as rich as its desktop equivalent. If we use JavaScript/HTML/CSS trio to build this UI then we definitely want to use AJAX. A typical approach is to use AJAX to update parts of our page using an HTML response, everyone knows that, right? Does this approach allow us to create a responsive, fast and flexible UI? The answer is no.
Here are 4 main downsides of using AJAX requests to load UI parts:
- You increase the server load – yes, you make an AJAX request only because you need a piece of HTML code that you want to insert to an already rendered page. That was cool a few years ago when AJAX was such a great innovation. Nowadays it should be considered as a less efficient solution.
- You complicate the server-side code – because you need parts of your page to be returned by the server you, obviously, need to handle that by writing more server-side code. You end up having many actions in your controllers that return different fragments of HTML. I know, you think it’s normal, everyone does that.
- You loose a lot of control over the UI – you use DHTML techniques to deal with the UI and in the same time you need the server to get parts of that UI. This leads to a code duplication and in many cases ends up with a big mess.
- A user will have to wait until a request is done – that just sucks, that poor guy has to wait because you went back to the server for a little piece of HTML. How you are going to implement a responsive UI this way? “Your servers are fast”. Of course they are, but what if user’s ISP sucks? What if the user is downloading something and 99% of his bandwidth is gone?
Rendering HTML on the client-side is ridiculously simple. Consider the following example. Let’s say we have a page with a list of some people, the list is just a simple HTML table. It could look like this:
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>E-Mail</th>
</tr>
</thead>
<tbody id="people">
<!-- here go people -->
<tbody>
<tfoot>
<tr colspan="4">
<td>
<a id="previous" href="#">Previous</a>
<a id="next" href="#">Next</a>
</td>
</tr>
</tfoot>
</table> |
As you can see the tbody element is empty, we will load its content after the entire page is loaded. Instead of a bunch of “tr” elements the server should return a nice JSON data which could look like this:
var people = [
{ id : 1, first_name : 'John', last_name : 'Doe', email : 'john.doe@somewhere.com' },
{ id : 2, first_name : 'Jane', last_name : 'Doe', email : 'jane.doe@anywhere.com' },
{ id : 3, first_name : 'Third', last_name : 'Guy', email : 'third.guy@nowhere.com' }
]; |
To render these data we obviously need a template. Prototype gives us a handy class called Template which is perfect for our needs. To create a template and render the list of people we need a few lines of JavaScript:
var personRowTemplate = new Template(
'<tr id="person_#{id}"><td>#{first_name}</td><td>#{last_name}</td><td>#{email}</td></tr>');
var peopleRows = '';
people.each(function(person) {
peopleRows += personRowTemplate.evaluate(person);
});
$('people').update(peopleRows); |
That’s pretty much it. It doesn’t look spectacular, huh? Now just think about the benefits of this approach:
- You have the data in JSON format, you can use them to render things like an edit form without going back to the server, thus user experience will be better
- You can be focused on building a nice JSON API for accessing data instead of implementing actions that return small pieces of HTML
- Your client-side code is cleaner and more consistent
Sounds like a good deal, doesn’t it?
Recently, new versions of jQuery and Prototype have been released – it’s a perfect moment for a part number 2. On the official Prototype blog we can read that the general performance of CSS selectors is now improved, unfortunately only for Safari 3, but Element#up/#down/#next/#previous should now be faster on all browsers, it’s a good news as they were really slow. On the other hand we have jQuery official announcement with information that jQuery is now 300% faster – we’ll see!
This time I made a step forward and decided to use a custom JavaScript-based testing environment instead of running tests using Firebug profiler. The obvious advantage is that I was able to run all the tests on 4 different browsers. New test cases aren’t much different then in the first part, let’s say it’s a modification of the previous ones with some extra operations and a little more complex HTML structure.
Test environment setup
Libraries:
- jQuery 1.2.2
- Prototype 1.6.0.2
All the tests were run on the following browsers:
- Firefox 2.0.0.11
- Konqueror 4.00.00
- Opera 9.50_beta1
- Internet Explorer 7 (*but using Windows on VirtualBox!*)
A tiny piece of JavaScript code is responsible for running the tests, each operation is called only once inside a try-catch block, so the essential part looks like this:
try {
var start = new Date;
test();
var end = new Date - start;
this.writeResults(test, end);
} catch(e) {
test.resultCell.innerHTML = '<div style="color: red">Exception caught: '+e.message+'</div>';
} |
There is a 3 seconds break between each test run, results are automatically inserted into the results table. If you want, you can check it out on your own, just go right here and hit the ‘run tests!’ button.
The results
I’m happy to see that all tests pass on the latest Konqueror, previous version from KDE3 fails on some Prototype tests. I don’t own Mac, so you won’t see Safari results here, although I’ve run the tests on my friend’s MacBook with very similar hardware as my laptop has (Intel Core Duo 2ghz + 2 gigs of RAM), and it was faster even then Konqueror (no, it doesn’t mean his MacBook is faster then my laptop!!!!
).
I’ve run everything 3 times, here are average results in ms:
| # |
Library |
Test |
Firefox |
Konqueror |
IE7 |
Opera |
| 1 |
jQuery |
$('td.counter').addClass('marked') |
|
96.6 |
32.3 |
70 |
37 |
| Prototype |
$$('td.counter').each(function(el){
el.addClassName('marked')
}) |
|
108.3 |
49.6 |
858 |
75.7 |
| 2 |
jQuery |
$('td.counter span.special').removeClass('special') |
|
62 |
23.6 |
46.6 |
25.6 |
| Prototype |
$$('td.counter span.special').each(function(el) {
el.removeClassName('special')
}) |
|
28 |
23.7 |
167 |
24.7 |
| 3 |
jQuery |
$('td.content span.odd').css('color', 'red') |
|
124.7 |
40.3 |
63.7 |
38.3 |
| Prototype |
$$('td.content span.odd').each(function(el) {
el.setStyle('color: red')
}) |
|
55.7 |
31 |
297 |
33.7 |
| 4 |
jQuery |
$('td.content span.even').before(
'<h3 style="display: none">text</h3>'
)</code |
|
382.7 |
177.3 |
373.7 |
205.3 |
| Prototype |
$$('td.content span.even').each(function(el) {
el.insert({
before:'<h3 style="display:none">text</h3>'
})
}) |
|
359 |
90.7 |
527 |
138.7 |
| 5 |
jQuery |
$('td.content h3').show() |
|
178.7 |
227.7 |
83.3 |
1161.7 |
| Prototype |
$$('td.content h3').each(Element.show) |
|
38 |
21 |
250.7 |
19 |
| 6 |
jQuery |
|
90 |
81.3 |
33.7 |
375.3 |
| Prototype |
$$('div.special').each(Element.hide) |
|
18 |
7 |
73.3 |
12 |
| 7 |
jQuery |
$('div.special, td.content .odd').toggle() |
|
637.7 |
431.7 |
517 |
1360.3 |
| Prototype |
$$('div.special, td.content .odd').each(Element.toggle) |
|
71 |
43.7 |
106.7 |
43 |
| 8 |
jQuery |
|
132.7 |
59.3 |
123.3 |
66.7 |
| Prototype |
$$('span.odd').each(Element.remove) |
|
29 |
11.7 |
36.7 |
19.3 |
| 9 |
jQuery |
$('#data p.lost:first').html('gotcha!') |
|
5 |
1.7 |
10 |
3.3 |
| Prototype |
$('data').down('p.lost').update('gotcha!') |
|
11.7 |
2 |
10 |
7.3 |
Conclusion #2
Prototype was at least 2 times faster then jQuery in 15 cases, and jQuery was faster then Prototype in 8 cases. What library should I choose? In my case I will stick with Prototype, because it offers the same functionality as jQuery does + more and it's faster. jQuery is probably better for projects where there's a need for some fancy UI effects and that's it, but it's just an assumption, correct me if I'm wrong...
Prototype 1.6.0 was released to the public 4 days ago, at first I decided to check its performance and new features in comparison to the previous version, but being interested in other JavaScript libraries, I’ve changed my mind. Lets see how you can accomplish same tasks with the latest Prototype and jQuery libraries and simply see which one is faster. In this part I’m going to show the results of running single operations, in next part(s) I will prepare a sample website and write more complex test cases and, again, check how fast jQuery and Prototype can be (or how slow…).
Test environment setup
The following software was used:
- Prototype 1.6.0
- jQuery 1.2.1
- Firefox 2.0.0.9
- Firebug 1.05
- Webdeveloper 1.1.4
Test page contains only one table with 500 rows, it’s generated using the following RHTML template:
<table>
<% (1..500).each do |i| %>
<tr>
<td class="first">
<div><%= i %></div>
</td>
<td class="second">
<div><%= "hello from #{i}" %></div>
</td>
</tr>
<% end %>
</table> |
Running the tests
Each operation was executed using Firebug’s console with Profiler turned on and repeated 3 times, between each execution the page was reloaded. Cache in Firefox was disabled using Webdeveloper plugin. Here are the results of 7 test operations showing average times and number of method calls in each operation:
1. Adding ‘marked’ CSS class to every cell having ‘first’ CSS class:
| Library |
Operation |
Time (ms) |
Method Calls |
| jQuery |
$('td.first').addClass('marked') |
|
102.495 |
3032 |
| Prototype #1 |
$$('td.first').each(function(cell) { cell.addClassName('marked') }); |
|
117.162 |
7056 |
| Prototype #2 |
$$('td.first').invoke('addClassName', 'marked'); |
|
129.972 |
8062 |
Comment: no practical difference, although jQuery is a little faster.
2. Removing CSS class from every table cell having “second” CSS class:
| Library |
Operation |
Time (ms) |
Method Calls |
| jQuery |
$('td.second').removeClass('second') |
|
109.855 |
3032 |
| Prototype #1 |
$$('td.second').each(function(cell) { cell.removeClassName('second') }); |
|
87.445 |
4551 |
| Prototype #2 |
$$('td.second').invoke('removeClassName', 'second'); |
|
100.64 |
5557 |
Comment: No big difference, especially when comparing Prototype #2 option with jQuery.
3. Setting ‘color’ CSS property in every div element which is inside a cell having ’second’ CSS class:
| Library |
Operation |
Time (ms) |
Method Calls |
| jQuery |
$('td.second div').css('color', 'red') |
|
173.906 |
3536 |
| Prototype #1 |
$$('td.second div').each(function(el) { el.setStyle({ color : 'red' }) }); |
|
87.585 |
4563 |
| Prototype #2 |
$$('td.second div').each(function(el) { el.setStyle('color : red') }); |
|
98.283 |
5064 |
| Prototype #3 |
$$('td.second div').invoke('setStyle', 'color : red'); |
|
107.81 |
6070 |
Comment: Same here, Prototype 2 x faster
4. Adding new elements before every div element which is inside a cell having ’second’ CSS class:
| Library |
Operation |
Time (ms) |
Method Calls |
| jQuery |
$('td.second div').before('<h3>text</h3>') |
|
368.611 |
15066 |
| Prototype #1 |
$$('td.second div').each(function(el) { el.insert({ before: '<h3>text</h3>' }) }); |
|
1252.016 |
17088 |
| Prototype #2 |
$$('td.second div').invoke('insert', { before: '<h3>text</h3>' }); |
|
1316.969 |
19096 |
Comment: Wow…jQuery is almost 4 times faster, I should point out that Element#insert is a new method introduced in Prototype 1.6.0.
5. Removing all div elements which are inside cells having ’second’ CSS class:
| Library |
Operation |
Time (ms) |
Method Calls |
| jQuery |
$('td.second div').remove() |
|
151.955 |
2032 |
| Prototype #1 |
$$('td.second div').each(Element.remove); |
|
65.785 |
3060 |
| Prototype #2 |
$$('td.second div').invoke('remove'); |
|
94,105 |
5068 |
| Prototype #3 |
$$('td.second div').each(function(el) { el.remove() }); |
|
83.591 |
4062 |
Comment: Prototype more then 2 times faster.
6. Hiding and un-hiding all div elements which are inside cells having ’second’ CSS class:
| Library |
Operation |
Time (ms) |
Method Calls |
| jQuery |
$('td.second div').toggle();$('td.second div').toggle(); |
|
1948.825 |
47653 |
| Prototype #1 |
$$('td.second div').each(Element.toggle);$$('td.second div').each(Element.toggle); |
|
158.987 |
14106 |
| Prototype #2 |
$$('td.second div').each(function(el) { el.toggle() }); $$('td.second div').each(function(el) { el.toggle() }); |
|
191.096 |
16110 |
Comment: jQuery tragedy, Prototype toggles visibility of 500 divs 10 times faster!
7. Attaching event listeners to all div elements which are inside cells having ’second’ CSS class:
| Library |
Operation |
Time (ms) |
Method Calls |
| jQuery |
$('td.second div').bind('click', function(){ alert(this.innerHTML) }) |
|
146.19 |
3535 |
| Prototype #1 |
$$('td.second div').invoke('observe', 'click', function(){ alert(this.innerHTML) }); |
|
169,889 |
11581 |
| Prototype #2 |
$$('td.second div').each(function(el) { el.observe('click', function(){ alert(this.innerHTML) }) }); |
|
164.456 |
10575 |
Comment: no practical difference.
General conclusion and impression
Executed tests show that Prototype seems to be faster then jQuery, with the exception of the new insertion method, which performance should be improved. Although I like jQuery syntax more then Prototype, the performance is way more important then saving few lines of code. Of course tests that I made don’t show how these libraries act in a real application, which is my task for the next part(s) of this article. Despite the results I must admit that I’m very excited about jQuery, my general impression is that this library is more mature then Prototype.
You may want to check out part ||