The way objects are used can have a dramatic impact on performance. Long object chains, large hashtables, repeated references, and brute-force object initialization can hinder performance.
Here are some way to avoid these problems:
| Ease | Effectiveness | Overall Practicality | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 7 |
|
8 |
An example of an object chain in Javascript:
window.document.forms[0];
Each dot in the object reference results in a lookup for that object.
If the object chain is referenced more than once, we can improve performance by introducing a temporary variable representing the longest common chain.
for example:
function setAutumnTheme() { if(typeof document.body != "undefined") { document.body.style.color = "#330000"; document.body.style.backgroundColor = "#ffffcc"; } }
Can be further reduced to:
function setAutumnTheme() { // Create a shortened object chain. var body = document.body; if(typeof body != "undefined") { body.style.color = "#330000"; body.style.backgroundColor = "#ffffcc"; } }
Do you see any other object chains that are used more than once? (Hint there is one more).
An expando property is an arbitrary property that can be added to any object. When an expando property is added, the property is searched through that object's lookup table. When we create custom objects, the overhead in searching through a large table, however slight, is avoided.
Expando properties will make the code less clear. By using clearly defined custom objects, the program becomes easier to understand. Remember, code that explicit is tunable.
(This demo is a variation of a demo provided by Microsoft™)
The proper alternative to using expando is the subject of the next topic: Custom Objects.
Speed up animation with custom objects. For the following examples, we will refer to the following custom object, which is optimized for fast performance.
ElementWrapper = function(el) {
this.el = el;
this.id = el.id;
this.css = this.el.style; // shorten object chain.
// use parseInt here once to avoid repeated calls in moveTo.
this.x = parseInt(this.css.left);
this.y = parseInt(this.css.top);
ElementWrappers[this.id] = this;
};
When accessing and setting the position of an element, most
developers use the element.style.left and
element.style.top properties.
This is inefficient because the left and top properties, like all other
CSS properties, are returned as a string. If we want to do math on the
returned value, it first needs to be converted from a string to an
integer by using parseInt. Here is an example:
ElementWrapper.prototype = {
/**
* Moves the element to a pixel coordinate.
* This works, but is not optimally efficient.
*/
moveTo : function(x, y) {
var left = parseInt(this.css.left);
var top = parseInt(this.css.top);
this.css.left = left + x + "px";
this.css.top = top + y + "px";
},
...
};
By using a custom object as a wrapper and by inlining assignment statements, better performance can be achieved in any browser.
ElementWrapper.prototype = {
moveTo : function(x, y) {
// inlining assignment for faster performance.
this.css.left = (this.x = x) + "px";
this.css.top = (this.y = y) + "px";
},
...
};
To store the ElementWrapper objects, we can use a custom lookup table, such as this:
ElementWrappers = { };
The ElementWrappers object will serve as a cache to
store ElementWrapper instances. This brings us to
the next topic: caching.
Creating a new object is more expensive than referencing a cached object.
Consider creating a regular expression pattern, or RegExp.
Function getTokenizedExp will create a regular expression
based on the parameters.
/**
* Return a regular expression based on the parameters.
* token - a string that is delimited by whitespace or string
* boundary points
* flag - a regexp flag. example:
* i - case-insensitive
*/
function getTokenizedExp(token, flag) {
return new new RegExp("(^|\\s)"+token+"($|\\s)", flag);
}
It works, and it's faster than using String methods like
indexOf and substring, but
if our program calls the function with the same parameters
more than once, the function will recreate a
regexp, wasting CPUs.
What if we could have a function that would return a
RegExp, but only create it if it hasn't
already been created.
The function will first look in the cache to see if the RegExp has been created. If the RegExp is in the cache, it will just return that RegExp. Otherwise (the RegExp is not in the cache) the function will create a new RegExp, store it in the cache, and then return it.
Here's how to realize that in code:
TokenizedExps = { };
function getTokenizedExp(token, flag){
var x = TokenizedExps[token+flag];
if(!x)
x = TokenizedExps[token+flag] =
new RegExp("(^|\\s)"+token+"($|\\s)", flag);
return x;
}
/**
* tests to see if String s contains token token.
*/
function hasToken(s, tok){
return getTokenizedExp(tok,"").test(s);
};
Here is a demo of the example code in action:
The same principal for caching RegExp objects
can also be applied to custom objects, such as
ElementWrapper instances.
Also called "initialize on demand", lazy initialization is a way of creating objects only when they are needed. Instead of initializing everything when the page loads, lazy initialization uses some event, such as a "click" or "mouseover" to initialize the object.
Lazy initialization and object caching are two techniques that tend to go hand in hand.
In JavaScript, the object's getInstance method
is usually called in a function that is called from an
event handler. It usually looks like this:
function handleClickedDiv(el) {
var ew = ElementWrapper.getInstance(el.id);
// do something with ew.
}
Initializing many objects in onload can make the browser noticeably unresponsive for a moment. The user perception will be greater than the actual performance hit, which is often less than a second!
Here's how we can define a getInstance function:
ElementWrappers = { };
ElementWrapper.getInstance = function(id) {
var x = ElementWrappers[id];
if(x == null)
x = ElementWrappers[id] =
new ElementWrapper(document.getElementById(id));
return x;
};
This technique can be used with custom object instances, as I have shown here, or it can be used with native object instances. The demos in item 4 show the benefits of of using cached regular expressions compared to using non-cached regular expressions.
Next in this tutorial: Loops: Iteration and Recursion