Efficient JavaScript
Document loading
Avoid keeping alive references from one document to another
If one document has accessed nodes or other objects from another document, avoid retaining those references after the script has finished using them. If a reference was stored in a global variable or a property of any long-living object in the current document, clear it by setting it to null, or deleting it.
The reason is that if the other document is destroyed, for instance if it was displayed in a popup window and that window is closed, any references to objects from that document will usually keep its entire DOM tree and scripting environment alive in RAM, even though the document itself is no longer loaded. The same will apply to pages within frames, inline frames, or OBJECT elements.
var remoteDoc = parent.frames['sideframe'].document;
var remoteContainer = remoteDoc.getElementById('content');
var newPara = remoteDoc.createElement('p');
newPara.appendChild(remoteDoc.createTextNode('new content'));
remoteContainer.appendChild(newPara);
//remove references
remoteDoc = null;
remoteContainer = null;
newPara = null;
Fast history navigation
Opera (and many other browsers) uses fast history navigation by default. When the user navigates forwards or backwards through their browsing history, the state of the page, and any scripts on it, is stored. When the user comes back to it, it continues as if they never left it. The document is not reloaded and reinitialized. Scripts continue to run, and DOM is as it was before they left the page. This results in a much faster response for the user, and can make slow loading Web applications perform much better during navigation.
Although Opera offers a way for authors to control this behaviour, it is better to allow it to use fast history navigation mode wherever possible. This means that if possible, scripts should try to avoid damaging actions that would cause this behaviour to fail. This includes things such as disabling form controls when a form is submitted, a menu that stops working after an item has been clicked, or a page fadeout effect that leaves the page content obscured or invisible.
A simple approach would be an onunload listener that resets the fading effect, or re-enables the form control. However, note that with some browsers, such as Firefox and Safari, adding a listener for the unload event will disable their fast history navigation. In addition, the act of disabling the submit button will be enough to disable fast history navigation in Opera.
window.onunload = function () {
document.body.style.opacity = '1';
};
Use XMLHttpRequest
This is not suited to every project, but it is an easy way to potentially reduce the amount of content being retrieved from the server, and avoiding the performance impact of destroying and recreating the scripting environment in between page loads. Initially, the page can be loaded as normal. Then for subsequent loads, XMLHttpRequest can be used to load minimal new content. This allows the JavaScript environment to persist.
Note, however, that this approach can lead to problems. In general, it breaks history navigation completely, and although it is possibles to fake this by storing information in inline frames, this defeats the purpose of using XMLHttpRequest in the first place. So use it sparingly, and only where it makes sense not to need to go back to earlier content. This approach can also confuse assistive devices, which may not be able to see the DOM of the page being changed, so it is best to use this only in situations where that will not cause a problem.
It will also fail if JavaScript is not available, or XMLHttpRequest is not supported. The easiest way to avoid this is to use a normal link, pointing to the new page. Add an event handler to that link that detects when the link is activated. The handler can then detect if XMLHttpRequest is supported, load the new data if it is, and then prevent the default action of the link. Once the new data has been loaded, it can be used to replace some of the content of the page, and the request object can then be destroyed to allow the memory to be retrieved by the garbage collector.
document.getElementById('nextlink').onclick = function () {
if( !window.XMLHttpRequest ) { return true; }
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if( request.readyState != 4 ) { return; }
var useResponse = request.responseText.replace( /^[\w\W]*<div id="container">|<\/div>\s*<\/body>[\w\W]*$/g , '' );
document.getElementById('container').innerHTML = useResponse;
request.onreadystatechange = null;
request = null;
};
request.open( 'GET', this.href, true );
request.send(null);
return false;
}
Create SCRIPT elements dynamically
Loading and processing a script can take time, but in some cases, a script may be loaded but never used. Loading it only wastes time and resources, stalling the current script execution while it does so, and it would be better not to load it at all if it is not going to be used. This can be done by a simple loader script that checks what other scripts are needed, and only creates the script element if the script will actually be used.
In theory, the extra scripts may be added after the page has loaded by creating a SCRIPT element and adding it to the document using DOM. This will work in current versions of all major browsers, but it may actually be placing more scripting demands on the browser than the script that it is loading. In addition, the script may be needed before the page has loaded, so it is best to check during page load, and create the script tags using document.write. Just remember to escape your forward slashes so you do not end the current script prematurely:
if( document.createElement && document.childNodes ) {
document.write('<script type="text\/javascript" src="dom.js"><\/script>');
}
if( window.XMLHttpRequest ) {
document.write('<script type="text\/javascript" src="xhr.js"><\/script>');
}
location.replace() keeps the history under control
Occasionally, it is necessary to change the page address using a script. The typical way to do this is by assigning a new address to location.href. This adds a history entry, and loads a new page, in the same way as activating a normal link.
In some cases, this extra history entry is unwanted, as the user will not need to go back to the earlier page. This is particularly useful if working in a situation where memory usage is critical. The memory used by the current page can be recovered by replacing the history entry instead. This is done using the location.replace() method.
location.replace('newpage.html');
Note that the page may still remain in cache, and may use memory there, but this will not be quite so much as if it were also kept in history.