We are in the middle of big project rewriting the web interface used by Insurance Agents to quote, order, and manage Auto policies for their clients. I can't go into a lot of details about the specific application but suffice it to say
there are a lot of fields on the form and that the code needs to access and re-access those fields over and over and over again.
That's easy to do with JavaScript, of course, but (there's always a "but")...when you do LOTS of this things tend to get slow.
Really slow.
Because we're writing this application as the base for a number of other products offered by our client, we have purposely built flexibility into it. So, where you might otherwise have hard-coded the number of vehicles or drivers, we have purposely built this application so those numbers can easily be changed. I already wrote about other aspects of this capability in a previous entry here.
Once you have all the flexibility of dynamically allowing the number of fields on the form to change, you can't exactly hard-code access to the fields (as the number of them can change at any moment). In JavaScript, that means you have to use a lot of eval() statements in your code, more or less like this:
var fieldRef;
for (var i = 1; i <= maxDrivers; i++){
fieldRef = eval("document.forms[0].DriverFirstName" + i);
// then do something here with fieldRef
}
Pretty standard stuff except that, as I said, it can be really slow, particularly if you need access to the same fields over and over again, which we do.
So, we came up with what has turned out to be a spectacularly successful (if I may say so) work-around.
It's not only that eval() is slow (which it is, particularly in IE), but also that you have to do it over and over again for the same field object. That's slow and inefficient. Happily, the solution is a model of simplicity: We built an array of pointers to fields.
Initially, the array has no values. When you ask for a pointer it first checks to see if it already has one to that particular field. If so, it returns the pointer (without having to go get it again). If it doesn't already have a pointed to the field you're looking for, the code creates the pointer, adds it to the array, and returns the pointer to you. Here's the code:
var getFieldObjList = new Array();
function getFieldObj(fName){
if (getFieldObjList[fName] == null){
getFieldObjList[fName] = document.forms[0][fName];
}
return getFieldObjList[fName];
}
That's it.
In your code, you substitute...
getFieldObjList(...)...for...
eval("document.forms[0]....")
...and it just works.
How well, you ask? In Internet Explorer (where this is most valuable), this technique cut the time to access 302 fields 26 times each (7,852 total accesses) from 2,994 milliseconds to 181. If you don't have your calculator handy, that's over 16 times faster.
In Firefox, the same code goes from 590 milliseconds to 181, a measly 3-times faster. So, it's good in Firefox but great in IE (or, more to the point, Firefox is a lot more efficient at evaluating code than IE).
If you're building complex JavaScript-intensive applications, you may want to consider adding this to your toolbox.
1. Rich Waters12/28/2006 04:47:00 PM
Homepage: http://www.rich-waters.com/blog/
I think the first question is why are you retrieving the fields over and over again?
I would hazard a guess that it would be more efficient to create an (javascript) object for each driver that has properties for name, address, etc.
This way you could create an array of objects and the data would already be stored in memory for javascript, relieving the need to traverse the DOM every time you wanted at a value. Though this would require some event handling if you needed the javascript values to stay up to date with the values that the user enters on the page.
Accessing the DOM should always be minimized when looking for best performance in a javascript heavy application.
2. Jack McKensie12/28/2006 05:10:30 PM
simpler to do this and be done with it:
fieldRef = document.forms[0].elements['DriverFirstName' + i];
No need for any global array of field references, they are already accessible from the DOM as above.
3. Scott Good12/29/2006 07:26:13 AM
Homepage: http://www.scottgood.com
@Rich: You may be right about creating objects for all the drivers and vehicles and such but I'm a little skeptical, at least in this application. While it probably wouldn't be necessary to keep the JavaScript objects in synch with the fields during editing (hm...maybe, maybe not), I'm not sure what benefit the objects would give you if you didn't because, ultimately, we're reading and updating field values here.
I can't see how keeping everything in synch with changes to the fields wouldn't add overhead, slowing the process a bit. Whereas I'm tracking nothing more than pointers to the fields, with your approach I'd have to update the objects in memory as well as the fields on the form. While that's not a lot more work it's still more work.
Also, the way this application is built there are duplications of data in places. For instance, some driver and vehicle information is carried over to a summary page. Any adjustments made in one place have to be pushed to the other, which requires a handle to the appropriate (secondary) field.
@Jack: Your approach works--and is measurably faster than using eval()--but in my testing it's pretty slow compared to the approach we're using. In a quick comparative test, my code ran in 181 milliseconds (8,000 field accesses) compared to 2,234 for yours. That's about 20% faster than using eval() (2,854 milliseconds), but about 45% slower than using hard-coded field names (1,540 milliseconds), and a bit more than 12 times slower than our approach (in IE 7, at least).
4. Rich Waters12/29/2006 11:10:51 AM
Homepage: http://www.rich-waters.com/blog/
Indeed, I made my comments without really knowing much about the application or what you were trying to accomplish. I was merely trying to point out that using fields on a page to store large amounts of data is not the most efficient. It's going to be much faster to access the data directly from javascript data structures. Especially if its not necessary to keep the display up to date. I see the idea behind using fields so that you can just normally submit the page to save changes, but its not much harder to push javascript data back to a server.
5. Scott Good12/29/2006 12:42:54 PM
Homepage: http://www.scottgood.com
Interesting. In this application I don't think that would work because the fields are needed for actual editing, but I'm interested in how you would use JavaScript to directly push data back to a Domino server. I've done it through iFrames using either forms or agents or both, but your post kinda sounds like you might have another scheme up your sleeve.
Scott
6. Rich Waters12/29/2006 06:08:05 PM
Homepage: http://www.rich-waters.com/blog/
On an application I was recently working on I pieced together a rough framework for making ajax calls to save changes to single or multiple documents. Mainly consisting of a couple of Java agents that processed post values from the ajax call and made the corresponding changes to documents. For the front-end I wrote a few wrappers on top of Prototype ( http://prototype.conio.net ) that boiled things down to some simple javascript functions that could be called to update fields on documents.
The main driving force for designing it that way was to ease changing fields across multiple documents at once. I also used the agents to process updates made from inline editing components that we added to several places throughout the application.
This is similar to how the DomYUI OpenNTF ( http://www.openntf.org/projects/pmt.nsf/ProjectLookup/Domino%20YUI ) project I recently started working on is going to add in inline editing capabilities to the data grid. (hopefully available in the next release)
7. Scott Good12/30/2006 08:06:31 AM
Homepage: http://www.scottgood.com
That's very interesting stuff! Let me know if you want some help on it.
Scott
8. Jack McKensie12/30/2006 07:53:01 PM
I remember reading on the IE Blog that repeated access to nested properties was slow and you should cache where possible so if you cached the reference to
document.forms[0].elements
You could do this:
var getFieldObjList = document.forms[0].elements;
and it should be just as fast. I'd be interested to know if it is.
9. Scott Good01/02/2007 08:53:29 AM
Homepage: http://www.scottgood.com
Jack,
From my quick testing, it's certainly better that way. It improved from 2,200 milliseconds to the point it's about the same as using hard-coded field names (1,500 milliseconds or so).
Of course, it's possible I'm not doing it right. Here's what I've done:
1. Created a global JS variable called documentElementsList as a blank array.
2. In the onLoad event, did this: documentElementsList = document.forms[0].elements;
3. In my loop, called for objects from the documentElementsList like this:
for (var x = 1; x <= 8; x++){
fieldRef = documentElementsList['DriverFirstName' + x].value;
}
Does that look right to you? It's interesting it made that kind of difference, even if it's not up to speed of the array of objects. Actually, the more we play with this, the more shocked I am at how well my little scheme is holding up...I did it the way I did only because I couldn't think of another approach. Lucky guess!
Thanks,
Scott
10. Scott Good01/02/2007 10:49:55 AM
Homepage: http://www.scottgood.com
Jack,
An update: Your code is almost exactly as fast as my code in Firefox.
Scott
11. Jack McKensie01/04/2007 08:46:31 PM
Hi Scott, this has been an interesting excersize!
I think it highlights the differences in the way the browser makers have done the bindings between the javascript compiler and the internal memory locations. I wonder if in IE document.forms[0]['element'] is more efficient than document.forms[0].elements['element'] ...hmmm
12. Scott Good01/05/2007 08:38:07 AM
Homepage: http://www.scottgood.com
Jack,
Just tried it. document.forms[0].elements['elementName'].value is 2,264 milliseconds (8,000 reads) vs 1,392 for the cached elements vs 170 for the array of objects (all in IE 7).
In Firefox, the same three are 1,001/621/551 ms.
So...either way you go ...elements['elementname'] is not a good value.
I have to say, though, I've learned a whole lot of new ways to access objects in JavaScript that I didn't know before we all started playing with this. As you say, very interesting.
Scott

























