By Jakob Heuser Published on May 6, 2008
Not too long ago, JavaScript made it possible to add nice effects to a page. And with the emergence of libraries, robust applications are emerging built on top of Prototype, Dojo, YUI, and other toolkits. Unfortunately, the people that pay the price for these so-called ‘thin clients’ are the end users. Every JavaScript file that needs to be loaded is a new connection to the server, and every connection to the server means more data; all this occurs before the end user even gets to interact with the page. Thankfully, there is a better way to do this—loading pieces of your web application as the user wants them.
The important things to address are page weight and load time. Both of these factors have a negative impact on the user, and we should be working towards minimizing it. Gaia Online and Zimbra have both talked recently about their lazy loading experiences. When the switch to lazy loading was made, both companies reduced their page weight by about 200KB and took at least two seconds off their load and initialization time.
Of course, lazy loading isn’t necessarily for everyone. If all of your site pages need all of your JavaScript before the page completes loading, then a lazy load won’t accomplish much. But if you’re working on a complex site or Web 2.0 application, read on!
Lazy Loading, Theory and Terminology
In conventional computer science, the lazy load pattern is a practical implementation of the Proxy Design Pattern. The goal is to avoid loading an object until it is absolutely needed—only then do we invest the resources in loading it. In the case of JavaScript, our application has a common set of function calls used by the web page. Our goal is to implement all of those functions with none of the “real” code. We’ll retrieve the real code later, when we need it. To a developer using our object, though, we want something that will look, act, and behave just like the original object. This specific implementation of the Proxy Design Pattern is called a Ghost.
In the Ghost pattern, we will provide an object that contains all of the public methods of our original object, but they will just be empty stubs with calls to our lazy load operation. When the object is actually used, we’ll load the real object, replacing our shell with the real one. We can then call the same method again, referencing the object we just loaded. Subsequent calls will hit the real object, as the shell no longer exists.
For this article, I’ll be using what is probably the most over-complicated Hello World object in the, um, world:
var HelloWorld = {
sayIt: function() {
alert('Hello, I came from script loaded on the fly!');
}
};
Our application has one public method—sayIt()—which provides an alert to the screen.
Lazy Loading With Dynamic Script Nodes in YUI and JIT
As an alternative to XHR-style includes of JavaScript files, some libraries such as YUI offer the creation of dynamic script nodes for including files. What these tools gain in cross-domain functionality and multiple asynchronous connections, they lose in ease of use. Additionally, since the call doesn’t wait to complete, some form of validation is needed before running our callback.
YUI’s Loader Utility has a few things it needs in order to work properly. We need to add the module’s information to the loader; tell the loader we require it; and then assign the callback to the loader’s onSuccess handler. In YUI, calling loader.insert() starts the loader, creating script nodes for all required modules and then calling the onSuccess callback when those nodes have completed:
var HelloWorld = {
is_loaded: false,
lazyLoad: function(callback) {
var loader = new YAHOO.util.YUILoader();
loader.addModule({
name: "helloworld",
type: "js",
fullpath: "yui_ex/helloworld.js"
});
loader.require("helloworld");
if (callback) {
loader.onSuccess = callback;
}
loader.insert();
},
sayIt: function() {
var args = arguments;
HelloWorld.lazyLoad(function() { HelloWorld.sayIt.apply(HelloWorld, args); });
}
};
YUI Lazy Load In Action
Similar to YUI’s Loader, the JIT Loader also uses dynamic script nodes but instead of managing packages, it instead manages loaded script URLs. The only other major difference is the addition of a verifier function, which tests our object to ensure it has finished loading before performing the callback. In our case, we’ll look at the HelloWorld.is_loaded property (bet you were wondering what that was for!) and wait until it changes to true, indicating our real object has been lazy loaded:
var HelloWorld = {
isLoaded: false,
lazyLoad: function(callback) {
JIT.loadOnce('jit_ex/helloworld.js',
function() {
return HelloWorld.is_loaded;
},
callback
);
},
sayIt: function() {
var args = arguments;
HelloWorld.lazyLoad(function() { HelloWorld.sayIt.apply(HelloWorld, args); });
}
};
Source: www.digital-web.com