Loading and Execution #
- JavaScript’s tendency to block browser process, both HTTP requests and UI updates, is the most notable performance issue.
- Since downloading and executing of scripts block the loading of resources and page, it’s recommended to place all <script> tags as close to the bottom of <body> tag as possible.
- We should limit the total number of <script> tags in the page, since every <script> tag would cause a delay.
- Instead of using two <script> tags to load two external script files, you can combine two file addresses to one.
Deferred Scripts #
A <script> tag with defer attribute indicates that the script contained within the element is not going to modify the DOM and therefor execution can be deferred safely.
<script type="text/javascript" src="file1.js" defer></script>
The JavaScript file (file1.js) will begin downloading at the point that <script> tag is parsed, but the code will not be execute until the DOM has been completely loaded, but before the onload event handler is called.
Dynamic Script Elements #
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "file1.js"
document.getElementsByTagName("head")[0].appendChild(script);The file begins downloading and executing after the element is added to the page, which means without blocking other page processes.
If the code in file1.js contains interfaces to be used by other scripts on the page, you need to track when the code has been fully downloaded and is ready for use.
When the src of a <script> element has been retrieved, it will fire a load event in Firefox, Opera, Chrome and Safari. In Internet Explorer, there is a alternate implementation named readystatechange event.
There are 5 possible values for readyState:
- uninitialized: The default state.
- loading: Download has begun.
- loaded: Download has completed.
- interactive: Data is completely downloaded but isn’t fully available.
- complete: All data is ready to be used.
The two states of most interest are “loaded” and “complete”. The safest to use the readystatechange event is to check for both of these states and remove the event handler when either one occurs, to ensure the event isn’t handled twice.
var script = document.createElement("script")
script.type = "text/javascript"
// For Internet Explorer
script.onreadystatechange = function() {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
alter("Script loaded.");
}
}
script.src = "file1.js";
document.getElementsByTagName("head")[0].appendChild(script);The following function encapsulates both the standard and IE-specific functionality:
function loadScript(url, callback) {
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState) { // IE
script.onreadystatechange = function() {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else {
script.onload = function() {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}You can dynamically load as many JavaScript files as necessary on a page, but make sure you consider the order in which files must be loaded. Only Firefox and Opera guarantee that the order of script execution will remain the same as you specify. Other browsers will download and execute the various code files in the order in which they are returned from the server. You can guarantee the order by chaining the downloads together, such as:
loadScript("file1.js", function () {
loadScript("file2.js", function() {
loadScript("file3.js", function() {
alter("All files are loaded");
})
})
})This approach can get a little bit difficult to manage if there are multiple files to download and execute. For multiple files, the preferred approach is to concatenate the files into a single file where each part is in the correct order.
Dynamic script loading is the most frequency used pattern for nonblocking JavaScript downloads due to its cross-browser compatibly and ease of use.
XMLHttpRequest Script Injection #
Another approach to nonblocking scripts is to retrieve the JavaScript code using an XMLHttpRequest(XHR) object and then inject the script into the page.
var xhr = new XMLHttpRequest();
xhr.open("get", "file1.js", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
var script = document.createElement("script");
script.type = "text/javascript";
script.text = xhr.responseText;
document.body.appendChild(script);
}
}
};
xhr.send(null);The primary advantage of this approach is that you can download the JavaScript code without executing it immediately. Since the code is being returned outside of a <script> tag, it won’t automatically be executed upon download, allowing you to defer its execution until you are ready. Another advantage is that the same code works in all modern browsers without exception cases.
The primary limitation of this approach is that the JavaScript file must be located on the same domain as the page requesting it, which makes downloading from CDNs impossible. So this approach isn’t used on large-scale web applications.
Recommended Nonblocking Pattern #
Two steps:
-
First, include the code necessary to dynamically load Javascript.
As small as possible, potentially containing just the
loadScript()function. -
Then, load the rest of the JavaScript code needed for page initialization.
Once the initial code is in place, use it to load the remaining JavaScript.
For example:
<script type="text/javascript" src="loader.js"></script> // loader.js contains loadScript() function
<script type="text/javascript">
// use loadScript() function to load other code dynamically
loadScript("the-rest.js", function() {
Application.init();
});
</script>Dynamic Script Loading Tools #
- YUI 3
- LazyLoad library
- LABjs library
Data Access #
Literal value and local variable access tend to be faster than array item and object member access.
Use literal values and local variables whenever possible and limit use of array items and object members, when speed is a concern.
Scope Chains Performance #
Every function inJavaScript is represented as an object—more specifically, as an instance of Function.
The deeper into the execution context’s scope chain an identifier exists, the slower it is to access for both reads and writes. Consequently, local variables are always the fastest to access inside of a function, whereas global variables will generally be the slowest.
A good rule of thumb is to always store out-of-scope values in local variables if they are used more than once within a function.
Prototype Chains #
A prototype is an object that serves as the base of another object, defining and implementing members that a new object must have.
Objects can have two types of members: instance members (also called own members) and prototype members. Instance members exist directly on the object instance itself, whereas prototype members are inherited from the object prototype.
The process of resolving an object member is very similar to resolving a variable. First search on the object instance, then search on the prototype object. The deeper into the prototype chain that a member exists, the slower it is to retrieve.
You can determine whether an object has an instance member with a given name by using the hasOwnProperty() method. To determine whether an object has access to a property (instance member and prototype member) with a given name, you can use the in operator.
var book = {
title: "High Performance JavaScript",
publisher: "Yahoo! Press"
};
alert(book.hasOwnProperty("title")); // true
alert(book.hasOwnProperty("toString")) // false
alert("title" in book); // true
alert("toString" in book); // trueNested Members #
Nested members cause the JavaScript engine to go through the object member resolution process each time a dot is encountered. The deeper the nested member, the slower the data is accessed.
If you are going to read an object property more than one time in a function, it best to store that property value in a local variable.
DOM Scripting #
DOM and JavaScript implementations are independent of each other. Think of DOM as a piece of land and JavaScript as another piece of land, both connected with a toll bridge. For performance, you should cross that bridge as few times as possible and strive to stay in JavaScript land.