Chapter 24

Best Practices

WHAT’S IN THIS CHAPTER?

  • Writing maintainable code
  • Ensuring code performance
  • Deploying code to production

The discipline of web development has grown at an extraordinary rate since 2000. What used to be a virtual Wild West, where just about anything was acceptable, has evolved into a complete discipline with research and established best practices. As simple websites grew into more complex web applications, and web hobbyists became paid professionals, the world of web development was filled with information about the latest techniques and development approaches. JavaScript, in particular, was the beneficiary of a lot of research and conjecture. Best practices for JavaScript fall into several categories and are handled at different points in the development process.

MAINTAINABILITY

In early websites, JavaScript was used primarily for small effects or form validation. Today’s web applications are filled with thousands of lines of JavaScript executing all types of complicated processes. This evolution requires that developers take maintainability into account. As with software engineers in more traditional disciplines, JavaScript developers are hired to create value for their company, and they do that not just by delivering products on time but also by developing intellectual property that continues to add value long after.

Writing maintainable code is important, because most developers spend a large amount of their time maintaining other people’s code. It’s a truly rare occurrence to be able to develop new code from scratch; it’s often the case that you must build on work that someone else has done. Making sure that your code is maintainable ensures that other developers can perform their jobs as well as possible.

image

Note that the concept of maintainable code is not unique to JavaScript. Some of these concepts apply broadly to any programming language, although there are some JavaScript-specific concepts as well.

What Is Maintainable Code?

Maintainable code has several characteristics. In general, code is said to be maintainable when it is all of the following:

  • Understandable — Someone else can pick up the code and figure out its purpose and general approach without a walk-through by the original developer.
  • Intuitive — Things in the code just seem to make sense, no matter how complex the operation.
  • Adaptable — The code is written in such a way that variances in data don’t require a complete rewrite.
  • Extendable — Care has been given in the code architecture to allow extension of the core functionality in the future.
  • Debuggable — When something goes wrong, the code gives you enough information to identify the issue as directly as possible.

Being able to write maintainable JavaScript code is an important skill for professionals. This is the difference between hobbyists who hack together a site over the weekend and professional developers who really know their craft.

Code Conventions

One of the simplest ways to start writing maintainable code is to come up with code conventions for the JavaScript that you write. Code conventions have been developed for most programming languages, and a quick Internet search is likely to turn up thousands of documents. Professional organizations have long instituted code conventions for developers in an attempt to make code more maintainable for everyone. The best-run open-source projects have strict code convention requirements that allow everyone in the community to easily understand how code is organized.

Code conventions are important for JavaScript because of the language’s adaptability. Unlike most object-oriented languages, JavaScript doesn’t force developers into defining everything as objects. The language can support any number of programming styles, from traditional object-oriented approaches to declarative approaches to functional approaches. A quick review of several open-source JavaScript libraries can easily yield multiple approaches to creating objects, defining methods, and managing the environment.

The following sections discuss the generalities of how to develop code conventions. These topics are important to address, although the way in which they are addressed may differ, depending on your individual needs.

Readability

For code to be maintainable, it must first be readable. Readability has to do with the way the code is formatted as a text file. A large part of readability has to do with the indentation of the code. When everyone is using the same indentation scheme, code across an entire project becomes much easier to read. Indentation is usually done by using a number of spaces instead of by using the tab character, which is typically displayed differently by different text editors. A good general indentation size is four spaces, although you may decide to use less or more.

Another part of readability is comments. In most programming languages, it’s an accepted practice to comment each method. Because of JavaScript’s ability to create functions at any point in the code, this is often overlooked. Because of this, it is perhaps even more important to document each function in JavaScript. Generally speaking, the places that should be commented in your code are as follows:

  • Functions and methods — Each function or method should include a comment that describes its purpose and possibly the algorithm being used to accomplish the task. It’s also important to state assumptions that are being made, what the arguments represent, and whether or not the function returns a value (since this is not discernible from a function definition).
  • Large sections of code — Multiple lines of code that are all used to accomplish a single task should be preceded with a comment describing the task.
  • Complex algorithms — If you’re using a unique approach to solve a problem, explain how you are doing it as a comment. This will not only help others who are looking at your code but also help you the next time you look at it.
  • Hacks — Because of browser differences, JavaScript code typically contains some hacks. Don’t assume that someone else who is looking at the code will understand the browser issue that such a hack is working around. If you need to do something differently because one of the browsers can’t use the normal way, put that in a comment. It reduces the likelihood that someone will come along, see your hack, and “fix” it, inadvertently introducing the bug that you had already worked around.

Indentation and comments create more readable code that is easier to maintain in the future.

Variable and Function Naming

The proper naming of variables and functions in code is vital to making it understandable and maintainable. Since many JavaScript developers began as hobbyists, there’s a tendency to use nonsensical names such as "foo" and "bar" for variables and names such as "doSomething" for functions. A professional JavaScript developer must overcome these old habits to create maintainable code. General rules for naming are as follows:

  • Variable names should be nouns, such as "car" or "person".
  • Function names should begin with a verb, such as getName(). Functions that return Boolean values typically begin with is, as in isEnabled().
  • Use logical names for both variables and functions, without worrying about the length. Length can be mitigated through postprocessing and compression (discussed later in this chapter).

It’s imperative to avoid useless variable names that don’t indicate the type of data they contain. With proper naming, code reads like a narrative of what is happening, making it easier to understand.

Variable Type Transparency

Since variables are loosely typed in JavaScript, it is easy to lose track of the type of data that a variable should contain. Proper naming mitigates this to some point, but it may not be enough in all cases. There are three ways to indicate the data type of a variable.

The first way is through initialization. When a variable is defined, it should be initialized to a value that indicates how it will be used in the future. For example, a variable that will hold a Boolean should be initialized to either true or false, and a variable to hold numbers should be initialized to a number, as in the following example:

//variable type indicated by initialization
var found = false;              //Boolean
var count  = -1;                //number     
var name = "";                  //string
var person = null;              //object

Initialization to a particular data type is a good indication of a variable’s type. The downside of initialization is that it cannot be used with function arguments in the function declaration.

The second way to indicate a variable’s type is to use Hungarian notation. Hungarian notation prepends one or more characters to the beginning of a variable to indicate the data type. This notation is popular among scripted languages and was, for quite some time, the preferred format for JavaScript as well. The most traditional Hungarian notation format for JavaScript prepends a single character for the basic data types: "o" for objects, "s" for strings, "i" for integers, "f" for floats, and "b" for Booleans. Here’s an example:

//Hungarian notation used to indicate data type
var bFound;      //Boolean
var iCount;      //integer
var sName;       //string
var oPerson;     //object

Hungarian notation for JavaScript is advantageous in that it can be used equally well for function arguments. The downside of Hungarian notation is that it makes code somewhat less readable, interrupting the intuitive, sentence-like nature of code that is accomplished without it. For this reason, Hungarian notation has started to fall out of favor among some developers.

The last way to indicate variable type is to use type comments. Type comments are placed right after the variable name but before any initialization. The idea is to place a comment indicating the data type right by the variable, as in this example:

//type comments used to indicate type
var found  /*:Boolean*/ = false;
var count  /*:int*/     = 10;
var name   /*:String*/  = "Nicholas";
var person /*:Object*/  = null;

Type comments maintain the overall readability of code while injecting type information at the same time. The downside of type comments is that you cannot comment out large blocks of code using multiline comments, because the type comments are also multiline comments that will interfere, as this example demonstrates:

//The following won't work correctly
/*
var found  /*:Boolean*/ = false;
var count  /*:int*/     = 10;
var name   /*:String*/  = "Nicholas";
var person /*:Object*/  = null;
*/

Here, the intent was to comment out all of the variables using a multiline comment. The type comments interfere with this because the first instance of /* (second line) is matched with the first instance of */ (third line), which will cause a syntax error. If you want to comment out lines of code using type comments, it’s best to use single-line comments on each line (many editors will do this for you).

These are the three most common ways to indicate the data type of variables. Each has advantages and disadvantages for you to evaluate before deciding on one. The important thing is to decide which works best for your project and use it consistently.

Loose Coupling

Whenever parts of an application depend too closely on one another, the code becomes too tightly coupled and hard to maintain. The typical problem arises when objects refer directly to one another in such a way that a change to one always requires a change to the other. Tightly coupled software is difficult to maintain and invariably has to be rewritten frequently.

Because of the technologies involved, there are several ways in which web applications can become too tightly coupled. It’s important to be aware of this and to try to maintain loosely coupled code whenever possible.

Decouple HTML/JavaScript

One of the most common types of coupling is HTML/JavaScript coupling. On the Web, HTML and JavaScript each represent a different layer of the solution: HTML is the data, and JavaScript is the behavior. Because they are intended to interact, there are a number of different ways to tie these two technologies together. Unfortunately, there are some ways that too tightly couple HTML and JavaScript.

JavaScript that appears inline in HTML, either using a <script> element with inline code or using HTML attributes to assign event handlers, is too tightly coupled. Consider the following code examples:

<!-- tightly coupled HTML/JavaScript using <script> -->
<script type="text/javascript">
  document.write("Hello world!");
</script>
                   
<!-- tightly coupled HTML/JavaScript using event handler attribute -->
<input type="button" value="Click Me" onclick="doSomething()" />

Although these are both technically correct, in practice they tightly couple the HTML representing the data with the JavaScript that defines the behavior. Ideally, HTML and JavaScript should be completely separate, with the JavaScript being included via external files and attaching behavior using the DOM.

When HTML and JavaScript are too tightly coupled, a JavaScript error means first determining whether the error occurred in the HTML portion of the solution or in a JavaScript file. It also introduces new types of errors related to the availability of code. In this example, the button may be clicked before the doSomething() function is available, causing a JavaScript error. Maintainability is affected because any change to the button’s behavior requires touching both the HTML and the JavaScript, when it should require only the latter.

HTML and JavaScript can also be too tightly coupled when the reverse is true: HTML is contained within JavaScript. This usually occurs when using innerHTML to insert a chunk of HTML text into the page, as in this example:

//tight coupling of HTML to JavaScript
function insertMessage(msg){
    var container = document.getElementById("container");
    container.innerHTML = "<div class="msg"><p class="post">" + msg + "</p>" +
        "<p><em>Latest message above.</em></p></div>";
}

Generally speaking, you should avoid creating large amounts of HTML in JavaScript. This, once again, has to do with keeping the layers separate and being able to easily identify the source of errors. When using this example code, a problem with page layout may be related to dynamically created HTML that is improperly formatted. However, locating the error may be difficult, because you would typically first view the source of the page to look for the offending HTML but wouldn’t find it there because it’s dynamically generated. Changes to the data or layout would also require changes to the JavaScript, which indicates that the two layers are too tightly coupled.

HTML rendering should be kept separate from JavaScript as much as possible. When JavaScript is used to insert data, it should do so without inserting markup whenever possible. Markup can typically be included and hidden when the entire page is rendered such that JavaScript can be used to display the markup later, instead of generating it. Another approach is to make an Ajax request to retrieve additional HTML to be displayed; this approach allows the same rendering layer (PHP, JSP, Ruby, and so on) to output the markup, instead of embedding it in JavaScript.

Decoupling HTML and JavaScript can save time during debugging, by making it easier to identify the source of errors, and it also eases maintainability: changes to behavior occur only in JavaScript files, whereas changes to markup occur only in rendering files.

Decouple CSS/JavaScript

Another layer of the web tier is CSS, which is primarily responsible for the display of a page. JavaScript and CSS are closely related: they are both layers on top of HTML and as such are often used together. As with HTML and JavaScript, however, it’s possible for CSS and JavaScript to be too tightly coupled. The most common example of tight coupling is using JavaScript to change individual styles, as shown here:

//tight coupling of CSS to JavaScript
element.style.color = "red";
element.style.backgroundColor = "blue";

Since CSS is responsible for the display of a page, any trouble with the display should be addressable by looking just at CSS files. However, when JavaScript is used to change individual styles, such as color, it adds a second location that must be checked and possibly changed. The result is that JavaScript is somewhat responsible for the display of the page and a tight coupling with CSS. If the styles need to change in the future, both the CSS and the JavaScript files may require changes. This creates a maintenance nightmare for developers. A cleaner separation between the layers is needed.

Modern web applications use JavaScript to change styles frequently, so although it’s not possible to completely decouple CSS and JavaScript, the coupling can be made looser. This is done by dynamically changing classes instead of individual styles, as in the following example:

//loose coupling of CSS to JavaScript
element.className = "edit";

By changing only the CSS class of an element, you allow most of the style information to remain strictly in the CSS. JavaScript can be used to change the class, but it’s not directly affecting the style of the element. As long as the correct class is applied, then any display issues can be tracked directly to CSS and not to JavaScript.

The second type of tight coupling is valid only in Internet Explorer (but not in Internet Explorer 8+ running in standards mode), where it’s possible to embed JavaScript in CSS via expressions, as in this example:

/* tight coupling of JavaScript to CSS */
div {
    width: expression(document.body.offsetWidth - 10 + "px");
}

Expressions are typically avoided because they’re not cross-browser-compatible. They should also be avoided because of the tight coupling between JavaScript and CSS that they introduce. When you use expressions, it’s possible that a JavaScript error can occur in CSS. Developers who have tried to track down a JavaScript error due to CSS expressions can tell you how long it took before they even considered looking at the CSS for the source of the error.

Once again, the importance of keeping a good separation of layers is paramount. The only source for display issues should be CSS, and the only source for behavior issues should be JavaScript. Keeping a loose coupling between these layers makes your entire application more maintainable.

Decouple Application Logic/Event Handlers

Every web application is typically filled with lots of event handlers listening for numerous different events. Few of them, however, take care to separate application logic from event handlers. Consider the following example:

function handleKeyPress(event){
    event = EventUtil.getEvent(event);
    if (event.keyCode == 13){
        var target = EventUtil.getTarget(event);
        var value = 5 * parseInt(target.value);
        if (value > 10){
            document.getElementById("error-msg").style.display = "block";
        }
    }
}

This event handler contains application logic in addition to handling the event. The problem with this approach is twofold. First, there is no way to cause the application logic to occur other than through the event, which makes it difficult to debug. What if the anticipated result didn’t occur? Does that mean that the event handler wasn’t called or that the application logic failed? Second, if a subsequent event causes the same application logic to occur, you’ll need to duplicate the functionality or else extract it into a separate function. Either way, it requires more changes to be made than are really necessary.

A better approach is to separate the application logic from event handlers, so that each handles just what it’s supposed to. An event handler should interrogate the event object for relevant information and then pass that information to some method that handles the application logic. For example, the previous code can be rewritten like this:

function validateValue(value){
    value = 5 * parseInt(value);
    if (value > 10){
        document.getElementById("error-msg").style.display = "block";
    }    
}
                   
function handleKeyPress(event){
    event = EventUtil.getEvent(event);
    if (event.keyCode == 13){
        var target = EventUtil.getTarget(event);
        validateValue(target.value);
    }
}

This updated code properly separates the application logic from the event handler. The handleKeyPress() function checks to be sure that the Enter key was pressed (event.keyCode is 13), and then gets the target of the event and passes the value property into the validateValue() function, which contains the application logic. Note that there is nothing in validateValue() that depends on any event handler logic whatsoever; it just receives a value and can do everything else based on that value.

Separating application logic from event handlers has several benefits. First, it allows you to easily change the events that trigger certain processes with a minimal amount of effort. If a mouse click initially caused the processing to occur, but now a key press should do the same, it’s quite easy to make that change. Second, you can test code without attaching events, making it easier to create unit tests or to automate application flow.

Here are a few rules to keep in mind for loose coupling of application and business logic:

  • Don’t pass the event object into other methods; pass only the data from the event object that you need.
  • Every action that is possible in the application should be possible without executing an event handler.
  • Event handlers should process the event and then hand off processing to application logic.

Keeping this approach in mind is a huge maintainability win in any code base, opening up numerous possibilities for testing and further development.

Programming Practices

Writing maintainable JavaScript isn’t just about how the code is formatted; it’s also about what the code does. Web applications created in an enterprise environment are often worked on by numerous people at the same time. The goal in these situations is to ensure that the browser environment in which everyone is working has constant and unchanging rules. To achieve this, developers should adhere to certain programming practices.

Respect Object Ownership

The dynamic nature of JavaScript means that almost anything can be modified at any point in time. It’s been said that nothing in JavaScript is sacred, as you’re unable to mark something as final or constant. This changed somewhat with ECMAScript 5’s introduction of tamper-proof objects (discussed in Chapter 22), but by default, all objects can be modified. In other languages, objects and classes are immutable when you don’t have the actual source code. JavaScript allows you to modify any object at any time, making it possible to override default behaviors in unanticipated ways. Because the language doesn’t impose limits, it’s important and necessary for developers to do so.

Perhaps the most important programming practice in an enterprise environment is to respect object ownership, which means that you don’t modify objects that don’t belong to you. Put simply: if you’re not responsible for the creation or maintenance of an object, its constructor, or its methods, you shouldn’t be making changes to it. More specifically:

  • Don’t add properties to instances or prototypes.
  • Don’t add methods to instances or prototypes.
  • Don’t redefine existing methods.

The problem is that developers assume that the browser environment works in a certain way. Changes to objects that are used by multiple people mean that errors will occur. If someone expects a function called stopEvent() to cancel the default behavior for an event, and you change it so it does that and also attaches other event handlers, it is certain that problems will follow. Other developers are assuming that the function just does what it did originally, so their usage will be incorrect and possibly harmful, because they don’t know the side effects.

These rules apply not only to custom types and objects but also to native types and objects such as Object, String, document, window, and so on. The potential issues here are even more perilous because browser vendors may change these objects in unannounced and unanticipated ways. An example of this occurred in the popular Prototype JavaScript library, which implemented the getElementsByClassName() method on the document object, returning an instance of Array that had also been augmented to include a method called each(). John Resig outlined on his blog the sequence of events that caused the issue. In his post (http://ejohn.org/blog/getelementsbyclassname-pre-prototype-16/), he noted that the problem occurred when browsers began to natively implement getElementsByClassName(), which returns not an Array but rather a NodeList that doesn’t have an each() method. Developers using the Prototype library had gotten used to writing code such as this:

document.getElementsByClassName("selected").each(Element.hide);

Although this code worked fine in browsers that didn’t implement getElementsByClassName() natively, it caused an error in the ones that did, as a result of the return value differences. You cannot anticipate how browser vendors will change native objects in the future, so modifying them in any way can lead to issues down the road when your implementation clashes with theirs.

The best approach, therefore, is to never modify objects you don’t own. You own an object only when you created it yourself, such as a custom type or object literal. You don’t own Array, document, or so on, because they were there before your code executed. You can still create new functionality for objects by doing the following:

  • Create a new object with the functionality you need, and let it interact with the object of interest.
  • Create a custom type that inherits from the type you want to modify. You can then modify the custom type with the additional functionality.

Many JavaScript libraries now subscribe to this theory of development, allowing them to grow and adapt even as browsers continually change.

Avoid Globals

Closely related to respecting object ownership is avoiding global variables and functions whenever possible. Once again, this has to do with creating a consistent and maintainable environment in which scripts will be executed. At most, a single global variable should be created on which other objects and functions exist. Consider the following:

//two globals - AVOID!!!
var name = "Nicholas";
function sayName(){
    alert(name);
}

This code contains two globals: the variable name and the function sayName(). These can easily be created on an object that contains both, as in this example:

//one global - preferred
var MyApplication = {
    name: "Nicholas",
    sayName: function(){
        alert(this.name);
    }
};

This rewritten version of the code introduces a single global object, MyApplication, onto which both name and sayName() are attached. Doing so clears up a couple of issues that existed in the previous code. First, the variable name overwrites the window.name property, which possibly interferes with other functionality. Second, it helps to clear up confusion over where the functionality lives. Calling MyApplication.sayName() is a logical hint that any issues with the code can be identified by looking at the code in which MyApplication is defined.

An extension of the single global approach is the concept of namespacing, popularized by the Yahoo! User Interface (YUI) library. Namespacing involves creating an object to hold functionality. In the 2.x version of YUI, there were several namespaces onto which functionality was attached. Here are some examples:

  • YAHOO.util.Dom — Methods for manipulating the DOM.
  • YAHOO.util.Event — Methods for interacting with events.
  • YAHOO.lang — Methods for helping with low-level language features.

For YUI, the single global object YAHOO serves as a container onto which other objects are defined. Whenever objects are used simply to group together functionality in this manner, they are called namespaces. The entire YUI library is built on this concept, allowing it to coexist on the same page with any other JavaScript library.

The important part of namespacing is to decide on a global object name that everyone agrees to use and that is unique enough that others aren’t likely to use it as well. In most cases, this can be the name of the company for which you’re developing the code, such as YAHOO or Wrox. You can then start creating namespaces to group your functionality, as in this example:

//create global object
var Wrox = {};
                   
//create namespace for Professional JavaScript
Wrox.ProJS = {};
                   
//attach other objects used in the book
Wrox.ProJS.EventUtil = { ... };
Wrox.ProJS.CookieUtil = { ... };

In this example, Wrox is the global on which namespaces are created. If all code for this book is placed under the Wrox.ProJS namespace, it leaves other authors to add their code onto the Wrox object as well. As long as everyone follows this pattern, there’s no reason to be worried that someone else will also write an object called EventUtil or CookieUtil, because it will exist on a different namespace. Consider this example:

//create namespace for Professional Ajax
Wrox.ProAjax = {};
                   
//attach other objects used in the book
Wrox.ProAjax.EventUtil = { ... };
Wrox.ProAjax.CookieUtil = { ... };
                   
//you can still access the ProJS one
Wrox.ProJS.EventUtil.addHandler( ... );
                   
//and the ProAjax one separately
Wrox.ProAjax.EventUtil.addHandler( ... );

Although namespacing requires a little more code, it is worth the trade-off for maintainability purposes. Namespacing helps ensure that your code can work on a page with other code in a nonharmful way.

Avoid Null Comparisons

Since JavaScript doesn’t do any automatic type checking, it becomes the developer’s responsibility. As a result, very little type checking actually gets done in JavaScript code. The most common type check is to see if a value is null. Unfortunately, checking a value against null is overused and frequently leads to errors due to insufficient type checking. Consider the following:

function sortArray(values){
    if (values != null){             //AVOID!!
        values.sort(comparator); 
    }
}

The purpose of this function is to sort an array with a given comparator. The values argument must be an array for the function to execute correctly, but the if statement simply checks to see that values isn’t null. There are several values that can make it past the if statement, including any string or any number, which would then cause the function to throw an error.

Realistically, null comparisons are rarely good enough to be used. Values should be checked for what they are expected to be, not for what they aren’t expected to be. For example, in the previous code, the values argument is expected to be an array, so you should be checking to see if it is an array, rather than checking to see if it’s not null. The function can be rewritten more appropriately as follows:

function sortArray(values){
    if (values instanceof Array){  //preferred
        values.sort(comparator); 
    }
}

This version of the function protects against all invalid values and doesn’t need to use null at all.

image

This technique for identifying an array doesn’t work properly in a multiframe web page, because each frame has its own global object and, therefore, its own Array constructor. If you are passing arrays from one frame to another, you may want to test for the existence of the sort() method instead.

If you see a null comparison in code, try replacing it using one of the following techniques:

  • If the value should be a reference type, use the instanceof operator to check its constructor.
  • If the value should be a primitive type, use the typeof operator to check its type.
  • If you’re expecting an object with a specific method name, use the typeof operator to ensure that a method with the given name exists on the object.

The fewer null comparisons in code, the easier it is to determine the purpose of the code and to eliminate unnecessary errors.

Use Constants

Even though JavaScript doesn’t have a formal concept of constants, they are still useful. The idea is to isolate data from application logic in such a way that it can be changed without risking the introduction of errors. Consider the following:

function validate(value){
    if (!value){
        alert("Invalid value!");
        location.href = "/errors/invalid.php";
    }
} 

There are two pieces of data in this function: the message displayed to the user and the URL. Strings that are displayed in the user interface should always be extracted in such a way as to allow for internationalization. URLs should also be extracted, because they have a tendency to change as an application grows. Basically, each of these has a possibility of changing for one reason or another, and a change would mean going into the function and changing code there. Any time you’re changing application logic code, you open up the possibility of creating errors. You can insulate application logic from data changes by extracting data into constants that are defined separately. Consider the following example:

var Constants = {
    INVALID_VALUE_MSG: "Invalid value!",
    INVALID_VALUE_URL: "/errors/invalid.php"
};
                   
function validate(value){
    if (!value){
        alert(Constants.INVALID_VALUE_MSG);
        location.href = Constants.INVALID_VALUE_URL;
    }
}

In this rewritten version of the code, both the message and the URL have been defined on a Constants object; the function then references these values. This setup allows the data to change without your ever needing to touch the function that uses it. The Constants object could even be defined in a completely separate file, and that file could be generated by some process that includes the correct values based on internationalization settings.

The key is to separate data from the logic that uses it. The types of values to look for are as follows:

  • Repeated values — Any values that are used in more than one place should be extracted into a constant. This limits the chance of errors when one value is changed but others are not. This includes CSS class names.
  • User interface strings — Any strings that are to be displayed to the user should be extracted for easier internationalization.
  • URLs — Resource locations tend to change frequently in web applications, so having a common place to store all URLs is recommended.
  • Any value that may change — Any time you’re using a literal value in code, ask yourself if this value might change in the future. If the answer is yes, then the value should be extracted into a constant.

Using constants is an important technique for enterprise JavaScript development, because it makes code more maintainable and keeps it safe from data changes.

PERFORMANCE

The amount of JavaScript developers now write per web page has grown dramatically since the language was first introduced. With that increase came concerns over the runtime execution of JavaScript code. JavaScript was originally an interpreted language, so the speed of execution was significantly slower than it was for compiled languages. Chrome was the first browser to introduce an optimizing engine that compiles JavaScript into native code. Since then, all other major browsers have followed suit and have implemented JavaScript compilation.

Even with the move to compiled JavaScript, it’s still possible to write slow code. However, there are some basic patterns that, when followed, ensure the fastest possible execution of code.

Be Scope-Aware

Chapter 4 discussed the concept of scopes in JavaScript and how the scope chain works. As the number of scopes in the scope chain increases, so does the amount of time it takes to access variables outside of the current scope. It is always slower to access a global variable than it is to access a local variable, because the scope chain must be traversed. Anything you can do to decrease the amount of time spent traversing the scope chain will increase overall script performance.

Avoid Global Lookups

Perhaps the most important thing you can do to improve the performance of your scripts is to be wary of global lookups. Global variables and functions are always more expensive to use than local ones because they involve a scope chain lookup. Consider the following function:

function updateUI(){
    var imgs = document.getElementsByTagName("img");
    for (var i=0, len=imgs.length; i < len; i++){
        imgs[i].title = document.title + " image " + i;
    }
                   
    var msg = document.getElementById("msg");
    msg.innerHTML = "Update complete."; 
}

This function may look perfectly fine, but it has three references to the global document object. If there are multiple images on the page, the document reference in the for loop could get executed dozens or hundreds of times, each time requiring a scope chain lookup. By creating a local variable that points to the document object, you can increase the performance of this function by limiting the number of global lookups to just one:

function updateUI(){
    var doc = document;
    var imgs = doc.getElementsByTagName("img");
    for (var i=0, len=imgs.length; i < len; i++){
        imgs[i].title = doc.title + " image " + i;
    }
                   
    var msg = doc.getElementById("msg");
    msg.innerHTML = "Update complete."; 
}

Here, the document object is first stored in the local doc variable. The doc variable is then used in place of document throughout the rest of the code. There’s only one global lookup in this function, compared to the previous version, ensuring that it will run faster.

A good rule of thumb is to store any global object that is used more than once in a function as a local variable.

Avoid the with Statement

The with statement should be avoided where performance is important. Similar to functions, the with statement creates its own scope and therefore increases the length of the scope chain for code executed within it. Code executed within a with statement is guaranteed to run slower than code executing outside, because of the extra steps in the scope chain lookup.

It is rare that the with statement is required, because it is mostly used to eliminate extra characters. In most cases, a local variable can be used to accomplish the same thing without introducing a new scope. Here is an example:

function updateBody(){
    with(document.body){
        alert(tagName);
        innerHTML = "Hello world!";
    }
}

The with statement in this code enables you to use document.body more easily. The same effect can be achieved by using a local variable, as follows:

function updateBody(){
    var body = document.body;
    alert(body.tagName);
    body.innerHTML = "Hello world!";
}

Although this code is slightly longer, it reads better than the with statement, ensuring that you know the object to which tagName and innerHTML belong. This code also saves global lookups by storing document.body in a local variable.

Choose the Right Approach

As with other languages, part of the performance equation has to do with the algorithm or approach used to solve the problem. Skilled developers know from experience which approaches are likely to achieve better performance results. Many of the techniques and approaches that are typically used in other programming languages can also be used in JavaScript.

Avoid Unnecessary Property Lookup

In computer science, the complexity of algorithms is represented using O notation. The simplest, and fastest, algorithm is a constant value or O(1). After that, the algorithms just get more complex and take longer to execute. The following table lists the common types of algorithms found in JavaScript.

NOTATION NAME DESCRIPTION
O(1) Constant Amount of time to execute remains constant no matter the number of values. Represents simple values and values stored in variables.
O(log n) Logarithmic Amount of time to execute is related to the number of values, but each value need not be retrieved for the algorithm to complete. Example: binary search.
O(n) Linear Amount of time to execute is directly related to the number of values. Example: iterating over all items in an array.
O(n2) Quadratic Amount of time to execute is related to the number of values such that each value must be retrieved at least n times. Example: insertion sort.

Constant values, or O(1), refer to both literals and values that are stored in variables. The notation O(1) indicates that the amount of time necessary to retrieve a constant value remains the same regardless of the number of values. Retrieving a constant value is an extremely efficient process and so is quite fast. Consider the following:

var value = 5;
var sum = 10 + value;
alert(sum);

This code performs four constant value lookups: the number 5, the variable value, the number 10, and the variable sum. The overall complexity of this code is then considered to be O(1).

Accessing array items is also an O(1) operation in JavaScript, performing just as well as a simple variable lookup. So the following code is just as efficient as the previous example:

var values = [5, 10];
var sum = values[0] + values[1];
alert(sum);

Using variables and arrays is more efficient than accessing properties on objects, which is an O(n) operation. Every property lookup on an object takes longer than accessing a variable or array, because a search must be done for a property of that name up the prototype chain. Put simply, the more property lookups there are, the slower the execution time. Consider the following:

var values = { first: 5, second: 10};
var sum = values.first + values.second;
alert(sum);

This code uses two property lookups to calculate the value of sum. Doing one or two property lookups may not result in significant performance issues, but doing hundreds or thousands will definitely slow down execution.

Be wary of multiple property lookups to retrieve a single value. For example, consider the following:

var query = window.location.href.substring(window.location.href.indexOf("?"));

In this code, there are six property lookups: three for window.location.href.substring() and three for window.location.href.indexOf(). You can easily identify property lookups by counting the number of dots in the code. This code is especially inefficient because the window.location.href value is being used twice, so the same lookup is done twice.

Whenever an object property is being used more than once, store it in a local variable. You’ll still take the initial O(n) hit to access the value the first time, but every subsequent access will be O(1), which more than makes up for it. For example, the previous code can be rewritten as follows:

var url = window.location.href;
var query = url.substring(url.indexOf("?"));

This version of the code has only four property lookups, a savings of 33 percent over the original. Making this kind of optimization in a large script is likely to lead to larger gains.

Generally speaking, any time you can decrease the complexity of an algorithm, you should. Replace as many property lookups as possible by using local variables to store the values. Furthermore, if you have an option to access something as a numeric array position or a named property (such as with NodeList objects), use the numeric position.

Optimize Loops

Loops are one of the most common constructs in programming and, as such, are found frequently in JavaScript. Optimizing these loops is an important part of the performance optimization process, since they run the same code repeatedly, automatically increasing execution time. There’s been a great deal of research done into loop optimization for other languages, and these techniques also apply to JavaScript. The basic optimization steps for a loop are as follows:

1. Decrement iterators — Most loops are created with an iterator that starts at 0 and is incremented up to a certain value. In many cases, it’s more efficient to start the iterator at the maximum number and decrement each time through the loop.

2. Simplify the terminal condition — Since the terminal condition is evaluated each time through the loop, it should be as fast as possible. This means avoiding property lookups or other O(n) operations.

3. Simplify the loop body — The body of the loop is executed the most, so make sure it’s as optimized as possible. Make sure there’s no intensive computation being performed that could easily be moved to outside the loop.

4. Use posttest loops — The most commonly used loops are for and while, both of which are pretest loops. Posttest loops, such as do-while, avoid the initial evaluation of the terminal condition and tend to run faster.

These changes are best illustrated with an example. The following is a basic for loop:

for (var i=0; i < values.length; i++){
    process(values[i]);
}

This code increments the variable i from 0 up to the total number of items in the values array. Assuming that the order in which the values are processed is irrelevant, the loop can be changed to decrement i instead, as follows:

for (var i=values.length-1; i >= 0; i--){
    process(values[i]);
}

Here, the variable i is decremented each time through the loop. In the process, the terminal condition is simplified by removing the O(n) call to values.length and replacing it with the O(1) call of 0. Since the loop body has only a single statement, it can’t be optimized further. However, the loop itself can be changed into a posttest loop like this:

var i=values.length-1;
if (i > -1){
    do {
        process(values[i]);
    }while(--i >= 0);
}

The primary optimization here is combining the terminal condition and the decrement operator into a single statement. At this point, any further optimization would have to be done to the process() function itself because the loop is fully optimized.

Keep in mind that using a posttest loop works only when you’re certain that there will always be at least one value to process. An empty array causes an unnecessary trip through the loop that a pre-test loop would otherwise avoid.

Unrolling Loops

When the number of times through a loop is finite, it is often faster to eliminate the loop altogether and replace it with multiple function calls. Consider the loop from the previous example. If the length of the array will always be the same, it may be more optimal to simply call process() on each item, as in the following code:

//eliminated the loop
process(values[0]);
process(values[1]);
process(values[2]);

This example assumes that there are only three items in the values array and simply calls process() directly on each item. Unrolling loops in this way eliminates the overhead of setting up a loop and processing a terminal condition, making the code run faster.

If the number of iterations through the loop can’t be determined ahead of time, you may want to consider using a technique called Duff’s device. The technique is named after its creator, Tom Duff, who first proposed using it in the C programming language. Jeff Greenberg is credited with implementing Duff’s device in JavaScript. The basic idea of Duff’s device is to unroll a loop into a series of statements by calculating the number of iterations as a multiple of 8. Consider the following code example:

//credit: Jeff Greenberg for JS implementation of Duff's Device
//assumes values.length > 0
var iterations = Math.ceil(values.length / 8);
var startAt = values.length % 8;
var i = 0;
                   
do {
    switch(startAt){
        case 0: process(values[i++]);
        case 7: process(values[i++]);
        case 6: process(values[i++]);
        case 5: process(values[i++]);
        case 4: process(values[i++]);
        case 3: process(values[i++]);
        case 2: process(values[i++]);
        case 1: process(values[i++]);
    }
    startAt = 0;
} while (--iterations > 0);

This implementation of Duff’s device starts by calculating how many iterations through the loop need to take place by dividing the total number of items in the values array by 8. The ceiling function is then used to ensure that the result is a whole number. The startAt variable holds the number of items that wouldn’t be processed if the iterations were based solely on dividing by 8. When the loop executes for the first time, the startAt variable is checked to see how many extra calls should be made. For instance, if there are 10 values in the array, startAt would be equal to 2, so process() would be called only twice the first time through the loop. At the bottom of the loop, startAt is reset to 0 so that each subsequent time through the loop results in eight calls to process(). This unrolling speeds up processing of large data sets.

The book Speed Up Your Site by Andrew B. King (New Riders, 2003) proposed an even faster Duff’s device technique that separated the do-while loop into two separate loops. Here’s an example:

//credit: Speed Up Your Site (New Riders, 2003)
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
                   
if (leftover > 0){
    do {
        process(values[i++]);
    } while (--leftover > 0);
}
                   
do {
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
} while (--iterations > 0);

In this implementation, the leftover count that wouldn’t have been handled in the loop when simply dividing by 8 is handled in an initial loop. Once those extra items are processed, execution continues in the main loop that calls process() eight times. This approach is almost 40 percent faster than the original Duff’s device implementation.

Unrolling loops can yield big savings for large data sets but may not be worth the extra effort for small data sets. The trade-off is that it takes more code to accomplish the same task, which is typically not worth it when large data sets aren’t being processed.

Avoid Double Interpretation

Double interpretation penalties exist when JavaScript code tries to interpret JavaScript code. This situation arises when using the eval() function or the Function constructor or when using setTimeout() with a string argument. Here are some examples:

//evaluate some code - AVOID!!
eval("alert('Hello world!')");
                   
//create a new function - AVOID!!
var sayHi = new Function("alert('Hello world!')");
                   
//set a timeout - AVOID!!
setTimeout("alert('Hello world!')", 500);

In each of these instances, a string containing JavaScript code has to be interpreted. This can’t be done during the initial parsing phase because the code is contained in a string, which means a new parser has to be started while the JavaScript code is running to parse the new code. Instantiating a new parser has considerable overhead, so the code runs slower than if it were included natively.

There are workarounds for all of these instances. It’s rare that eval() is absolutely necessary, so try to avoid it whenever possible. In this case, the code could just be included inline. For the Function constructor, the code can be rewritten as a regular function quite easily, and the setTimeout() call can pass in a function as the first argument. Here are some examples:

//fixed
alert('Hello world!'),
                   
//create a new function - fixed
var sayHi = function(){
    alert('Hello world!'),
};
                   
//set a timeout - fixed
setTimeout(function(){
    alert('Hello world!'),
}, 500);

To increase the performance of your code, avoid using strings that need to be interpreted as JavaScript whenever possible.

Other Performance Considerations

There are a few other things to consider when evaluating the performance of your script. The following aren’t major issues, but they can make a difference when used frequently:

  • Native methods are fast — Whenever possible, use a native method instead of one written in JavaScript. Native methods are written in compiled languages such as C or C++ and thus run much faster than those in JavaScript. The most often forgotten methods in JavaScript are the complex mathematical operations available on the Math object; these methods always run faster than any JavaScript equivalent for calculating sine, cosine, and so on.
  • Switch statements are fast — If you have a complex series of if-else statements, converting it to a single switch statement can result in faster code. You can further improve the performance of switch statements by organizing the cases in the order of most likely to least likely.
  • Bitwise operators are fast — When performing mathematical operations, bitwise operations are always faster than any Boolean or numeric arithmetic. Selectively replacing arithmetic operations with bitwise operations can greatly improve the performance of complex calculations. Operations such as modulus, logical AND, and logical OR are good candidates to be replaced with bitwise operations.

Minimize Statement Count

The number of statements in JavaScript code affects the speed with which the operations are performed. A single statement can complete multiple operations faster than multiple statements each performing a single operation. The task, then, is to seek out statements that can be combined in order to decrease the execution time of the overall script. To do so, you can look for several patterns.

Multiple Variable Declarations

One area in which developers tend to create too many statements is in the declaration of multiple variables. It’s quite common to see code declaring multiple variables using multiple var statements, such as the following:

//four statements - wasteful
var count = 5;
var color = "blue";
var values = [1,2,3];
var now = new Date();

In strongly typed languages, variables of different data types must be declared in separate statements. In JavaScript, however, all variables can be declared using a single var statement. The preceding code can be rewritten as follows:

//one statement
var count = 5,
    color = "blue",
    values = [1,2,3],
    now = new Date();

Here, the variable declarations use a single var statement and are separated by commas. This is an optimization that is easy to make in most cases and performs much faster than declaring each variable separately.

Insert Iterative Values

Any time you are using an iterative value (that is, a value that is being incremented or decremented at various locations), combine statements whenever possible. Consider the following code snippet:

var name = values[i];
i++;

Each of the two preceding statements has a single purpose: the first retrieves a value from values and stores it in name; the second increments the variable i. These can be combined into a single statement by inserting the iterative value into the first statement, as shown here:

var name = values[i++];

This single statement accomplishes the same thing as the previous two statements. Because the increment operator is postfix, the value of i isn’t incremented until after the rest of the statement executes. Whenever you have a similar situation, try to insert the iterative value into the last statement that uses it.

Use Array and Object Literals

Throughout this book, you’ve seen two ways of creating arrays and objects: using a constructor or using a literal. Using constructors always leads to more statements than are necessary to insert items or define properties, whereas literals complete all operations in a single statement. Consider the following example:

//four statements to create and initialize array - wasteful
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
                   
//four statements to create and initialize object - wasteful
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.sayName = function(){
    alert(this.name);
};

In this code, an array and an object are created and initialized. Each requires four statements: one to call the constructor and three to assign data. These can easily be converted to use literals as follows:

//one statement to create and initialize array
var values = [123, 456, 789];
                   
//one statement to create and initialize object
var person = {
    name : "Nicholas",
    age : 29,
    sayName : function(){
        alert(this.name);
    }
};

This rewritten code contains only two statements: one to create and initialize the array, and one to create and initialize the object. What previously took eight statements now takes only two, reducing the statement count by 75 percent. The value of these optimizations is even greater in codebases that contain thousands of lines of JavaScript.

Whenever possible, replace your array and object declarations with their literal representation to eliminate unnecessary statements.

image

There is a slight performance penalty for using literals in Internet Explorer 6 and earlier. These issues are resolved in Internet Explorer 7.

Optimize DOM Interactions

Of all the parts of JavaScript, the DOM is without a doubt the slowest part. DOM manipulations and interactions take a large amount of time because they often require rerendering all or part of the page. Furthermore, seemingly trivial operations can take longer to execute because the DOM manages so much information. Understanding how to optimize interactions with the DOM can greatly increase the speed with which scripts complete.

Minimize Live Updates

Whenever you access part of the DOM that is part of the displayed page, you are performing a live update. Live updates are so called because they involve immediate (live) updates of the page’s display to the user. Every change, whether it be inserting a single character or removing an entire section, incurs a performance penalty as the browser recalculates thousands of measurements to perform the update. The more live updates you perform, the longer it will take for the code to completely execute. The fewer live updates necessary to complete an operation, the faster the code will be. Consider the following example:

var list = document.getElementById("myList"),
           item,
           i;
                   
for (i=0; i < 10; i++) {
    item = document.createElement("li");
    list.appendChild(item);
    item.appendChild(document.createTextNode("Item " + i));
}

This code adds 10 items to a list. For each item that is added, there are two live updates: one to add the <li> element and another to add the text node to it. Since 10 items are being added, that’s a total of 20 live updates to complete this operation.

To fix this performance bottleneck, you need to reduce the number of live updates. There are generally two approaches to this. The first is to remove the list from the page, perform the updates, and then reinsert the list into the same position. This approach is not ideal because it can cause unnecessary flickering as the page updates each time. The second approach is to use a document fragment to build up the DOM structure and then add it to the list element. This approach avoids live updates and page flickering. Consider the following:

var list = document.getElementById("myList"),
    fragment = document.createDocumentFragment(),
    item,
    i
                   
for (i=0; i < 10; i++) {
    item = document.createElement("li");
    fragment.appendChild(item);
    item.appendChild(document.createTextNode("Item " + i));
}
                   
list.appendChild(fragment);

There is only one live update in this example, and it occurs after all items have been created. The document fragment is used as a temporary placeholder for the newly created items. All items are then added to the list, using appendChild(). Remember, when a document fragment is passed in to appendChild(), all of the children of the fragment are appended to the parent, but the fragment itself is never added.

Whenever updates to the DOM are necessary, consider using a document fragment to build up the DOM structure before adding it to the live document.

Use innerHTML

There are two ways to create new DOM nodes on the page: using DOM methods such as createElement() and appendChild(), and using innerHTML. For small DOM changes, the two techniques perform roughly the same. For large DOM changes, however, using innerHTML is much faster than creating the same DOM structure using standard DOM methods.

When innerHTML is set to a value, an HTML parser is created behind the scenes, and the DOM structure is created using the native DOM calls rather than JavaScript-based DOM calls. The native methods execute much faster, since they are compiled rather than interpreted. The previous example can be rewritten to use innerHTML like this:

var list = document.getElementById("myList"),
    html = "",
    i;
                   
for (i=0; i < 10; i++) {
    html += "<li>Item " + i + "</li>";
}
                   
list.innerHTML = html;

This code constructs an HTML string and then assigns it to list.innerHTML, which creates the appropriate DOM structure. Although there is always a small performance hit for string concatenation, this technique still performs faster than performing multiple DOM manipulations.

The key to using innerHTML, as with other DOM operations, is to minimize the number of times it is called. For instance, the following code uses innerHTML too much for this operation:

var list = document.getElementById("myList"),
    i;
                   
for (i=0; i < 10; i++) {
    list.innerHTML += "<li>Item " + i + "</li>";     //AVOID!!!
}

The problem with this code is that innerHTML is called each time through the loop, which is incredibly inefficient. A call to innerHTML is, in fact, a live update and should be treated as such. It’s far faster to build up a string and call innerHTML once than it is to call innerHTML multiple times.

Use Event Delegation

Most web applications make extensive use of event handlers for user interaction. There is a direct relationship between the number of event handlers on a page and the speed with which the page responds to user interaction. To mitigate these penalties, you should use event delegation whenever possible.

Event delegation, as discussed in Chapter 13, takes advantage of events that bubble. Any event that bubbles can be handled not just at the event target but also at any of the target’s ancestors. Using this knowledge, you can attach event handlers at a high level that are responsible for handling events for multiple targets. Whenever possible, attach an event handler at the document level that can handle events for the entire page.

Beware of HTMLCollections

The pitfalls of HTMLCollection objects have been discussed throughout this book, because they are a big performance sink for web applications. Keep in mind that any time you access an HTMLCollection, whether it be a property or a method, you are performing a query on the document, and that querying is quite expensive. Minimizing the number of times you access an HTMLCollection can greatly improve the performance of a script.

Perhaps the most important area in which to optimize HTMLCollection access is loops. Moving the length calculation into the initialization portion of a for loop was discussed previously. Now consider this example:

var images = document.getElementsByTagName("img"),
    i, len;
                   
for (i=0, len=images.length; i < len; i++){
    //process
}

The key here is that the length is stored in the len variable instead of constantly accessing the length property of the HTMLCollection. When using an HTMLCollection in a loop, you should make your next step a retrieval of a reference to the item you’ll be using, as shown here, so as to avoid calling the HTMLCollection multiple times in the loop body:

var images = document.getElementsByTagName("img"),
    image,
    i, len;
                   
for (i=0, len=images.length; i < len; i++){
    image = images[i];
    //process
}

This code adds the image variable, which stores the current image. Once this is complete, there should be no further reason to access the images HTMLCollection inside the loop.

When writing JavaScript, it’s important to realize when HTMLCollection objects are being returned, so you can minimize accessing them. An HTMLCollection object is returned when any of the following occurs:

  • A call to getElementsByTagName() is made.
  • The childNodes property of an element is retrieved.
  • The attributes property of an element is retrieved.
  • A special collection is accessed, such as document.forms, document.images, and so forth.

Understanding when you’re using HTMLCollection objects and making sure you’re using them appropriately can greatly speed up code execution.

DEPLOYMENT

Perhaps the most important part of any JavaScript solution is the final deployment to the website or web application in production. You’ve done a lot of work before this point, architecting and optimizing a solution for general consumption. It’s time to move out of the development environment and into the Web, where real users can interact with it. Before you do so, however, there are a number of issues that need to be addressed.

Build Process

One of the most important things you can do to ready JavaScript code for deployment is to develop some type of build process around it. The typical pattern for developing software is write-compile-test, in that you write the code, compile it, and then run it to ensure that it works. Since JavaScript is not a compiled language, the pattern has become write-test, where the code you write is the same code you test in the browser. The problem with this approach is that it’s not optimal; the code you write should not be passed, untouched, to the browser, for the following reasons:

  • Intellectual property issues — If you put the fully commented source code online, it’s easier for others to figure out what you’re doing, reuse it, and potentially figure out security holes.
  • File size — You write code in a way that makes it easy to read, which is good for maintainability but bad for performance. The browser doesn’t benefit from the extra white space, indentation, or verbose function and variable names.
  • Code organization — The way you organize code for maintainability isn’t necessarily the best way to deliver it to the browser.

For these reasons, it’s best to define a build process for your JavaScript files.

A build process starts by defining a logical structure for storing your files in source control. It’s best to avoid having a single file that contains all of your JavaScript. Instead, follow the pattern that is typically taken in object-oriented languages: separate each object or custom type into its own file. Doing so ensures that each file contains just the minimum amount of code, making it easier to make changes without introducing errors. Additionally, in environments that use concurrent source control systems such as CVS or Subversion, this reduces the risk of conflicts during merge operations.

Keep in mind that separating your code into multiple files is for maintainability and not for deployment. For deployment, you’ll want to combine the source files into one or more rollup files. It’s recommended that web applications use the smallest number of JavaScript files possible, because HTTP requests are some of the main performance bottlenecks on the Web. Keep in mind that including a JavaScript file via the <script> tag is a blocking operation that stops all other downloads while the code is downloaded and executed. Therefore, try to logically group JavaScript code into deployment files.

Once you’ve organized your file and directory structure, and determined what should be in your deployment files, you’ll want to create a build system. The Ant build tool (http://ant.apache.org) was created to automate Java build processes but has gained popularity with web application developers because of its ease of use and coverage by software engineers, such as Julien Lecomte, who have written tutorials explaining how to use Ant for JavaScript and CSS build automation (Lecomte’s article can be found at www.julienlecomte.net/blog/2007/09/16/).

Ant is ideal for a JavaScript build system because of its simple file-manipulation capabilities. For example, you can easily get a list of all files in a directory and then combine them into a single file, as shown here:

image
<project name="JavaScript Project" default="js.concatenate">
                   
    <!-- the directory to output to -->
    <property name="build.dir" value="./js" />
                   
    <!-- the directory containing the source files -->
    <property name="src.dir" value="./dev/src" />
                   
    <!-- Target to concatenate all JS files -->
    <!-- Credit: Julien Lecomte, http://www.julienlecomte.net/blog/2007/09/16/ -->
    <target name="js.concatenate">
        <concat destfile="${build.dir}/output.js">
            <filelist dir="${src.dir}/js" files="a.js, b.js"/>
            <fileset dir="${src.dir}/js" includes="*.js" excludes="a.js, b.js"/>
        </concat>
    </target>
                   
</project>

SampleAntDir/build.xml

This build.xml file defines two properties: a build directory into which the final file should be output and a source directory where the JavaScript source files exist. The target js.concatenate uses the <concat> element to specify a list of files that should be concatenated and the location where the resulting file should be output. The <filelist> element is used to indicate that the files a.js and b.js should be first in the concatenated file, and the <fileset> element indicates that all of the other files in the directory, with the exception of a.js and b.js, should be added afterwards. The resulting file will be output to /js/output.js.

With Ant installed, you can go to the directory in which this build.xml file exists, and run this code snippet:

ant

The build process is then kicked off, and the concatenated file is produced. If there are other targets in the file, you can execute just the js.concatenate target, using the following code:

ant js.concatenate

Depending on your needs, the build process can be changed to include more or less steps. Introducing the build step to your development cycle gives you a location where you can add more processing for JavaScript files prior to deployment.

Validation

Even though IDEs that understand and support JavaScript are starting to appear, most developers still check their syntax by running code in a browser. There are a couple of problems with this approach. First, this validation can’t be easily automated or ported from system to system. Second, aside from syntax errors, problems are encountered only when code is executed, leaving it possible for errors to occur. There are several tools available to help identify potential issues with JavaScript code, the most popular being Douglas Crockford’s JSLint (www.jslint.com).

JSLint looks for syntax errors and common coding errors in JavaScript code. Some of the potential issues it surfaces are as follows:

  • Use of eval()
  • Use of undeclared variables
  • Omission of semicolons
  • Improper line breaks
  • Incorrect comma usage
  • Omission of braces around statements
  • Omission of break in switch cases
  • Variables being declared twice
  • Use of with
  • Incorrect use of equals (instead of double- or triple-equals)
  • Unreachable code

The online version is available for easy access, but it can also be run on the command line using the Java-based Rhino JavaScript engine (www.mozilla.org/rhino/). To run JSLint on the command line, you first must download Rhino and then download the Rhino version of JSLint from www.jslint.com/rhino/. Once it is installed, you can run JSLint on the command line using the following syntax:

java -jar rhino-1.6R7.jar jslint.js [input files]

Here is an example:

java -jar rhino-1.6R7.jar jslint.js a.js b.js c.js

If there are any syntax issues or potential errors in the given files, a report is output with the errors and warnings. If there are no issues, then the code completes without displaying any messages.

You can run JSLint as part of your build process using Ant with a target such as this:

image
<target name="js.verify">
    <apply executable="java" parallel="false">
        <fileset dir="${build.dir}" includes="output.js"/>
        <arg line="-jar"/>
        <arg path="${rhino.jar}"/>
        <arg path="${jslint.js}" />
        <srcfile/>
    </apply>
</target>

SampleAntDir/build.xml

This target assumes that the location of the Rhino jar file is specified in a property called rhino.jar, and the location of the JSLint Rhino file is specified as a property called jslint.js. The output.js file is passed into JSLint to be verified and will output any issues that it finds.

Adding code validation to your development cycle helps to avoid errors down the road. It’s recommended that developers add some type of code validation to the build process as a way of identifying potential issues before they become errors.

image

A list of JavaScript code validators can be found in Appendix D.

Compression

When talking about JavaScript file compression, you’re really talking about two things: code size and wire weight. Code size refers to the number of bytes that need to be parsed by the browser, and wire weight refers to the number of bytes that are actually transmitted from the server to the browser. In the early days of web development, these two numbers were almost always identical, because source files were transmitted, unchanged, from server to client. In today’s Web, however, the two are rarely equal and realistically should never be.

File Compression

Because JavaScript isn’t compiled into byte code and is actually transmitted as source code, the code files often contain additional information and formatting that isn’t necessary for browser execution. Comments, extra white space, and long variable or function names improve readability for developers but are unnecessary extra bytes when sent to the browser. You can, however, decrease the file size using a compressor tool.

Compressors typically perform some or all of the following steps:

  • Remove extra white space (including line breaks)
  • Remove all comments
  • Shorten variable names

There are many compressors available for JavaScript (a full list is included in Appendix D), but the best is arguably the YUI Compressor, available at http://developer.yahoo.com/yui/compressor/. The YUI Compressor uses the Rhino JavaScript parser to tokenize JavaScript code. This token stream can then be used to create an optimal version of the code without white space or comments. Unlike regular expression-based compressors, the YUI Compressor is guaranteed to not introduce syntax errors and can therefore safely shorten local variable names.

The YUI Compressor comes as a Java jar file named yuicompressor-x.y.z.jar, where x.y.z is the version number. At the time of this writing, 2.4.6 is the most recent version. You can execute the YUI Compressor using the following command-line format:

java -jar yuicompressor-x.y.z.jar [options] [input file]

Options for the YUI Compressor are listed in the following table.

OPTION DESCRIPTION
-h Displays help information.
-o outputFile Specifies the name of the output file. If not included, the output file name is the input file name appended with “-min”. For example, an input file of input.js would produce input-min.js.
--line-break column Indicates to include a line break after the column number of characters. By default, the compressed file is output on one line, which may cause issues in some source control systems.
-v, --verbose Verbose mode. Outputs hints for better compression and warnings.
--charset charset Indicates the character set that the input file is in. The output file will use the same character set.
--nomunge Turns off local variable name replacement.
--disable-optimizations Turns off YUI Compressor’s micro-optimizations.
--preserve-semi Preserves unnecessary semicolons that would otherwise have been removed.

For example, the following can be used to compress the CookieUtil.js file into a file named simply cookie.js:

java -jar yuicompressor-2.3.5.jar -o cookie.js CookieUtil.js

The YUI Compressor can also be used from Ant by calling the java executable directly, as in this example:

image
<!-- Credit: Julien Lecomte, http://www.julienlecomte.net/blog/2007/09/16/ -->
<target name="js.compress">
    <apply executable="java" parallel="false">
        <fileset dir="${build.dir}" includes="output.js"/>
        <arg line="-jar"/>
        <arg path="${yuicompressor.jar}"/>
        <arg line="-o ${build.dir}/output-min.js"/>
        <srcfile/>
    </apply>
</target>

SampleAntDir/build.xml

This target includes a single file, the output.js file created as part of the build process, and passes it to the YUI Compressor. The output file is specified as output-min.js in the same directory. This assumes that the property yuicompressor.jar contains the location of the YUI Compressor jar file. You can run this target using the following command:

ant js.compress

All JavaScript files should be compressed using the YUI Compressor or a similar tool before being deployed to a production environment. Adding a step in your build process to compress JavaScript files is an easy way to ensure that this always happens.

HTTP Compression

Wire weight refers to the actual number of bytes sent from the server to the browser. The number of bytes doesn’t necessarily have to be the same as the code size, because of the compression capabilities of both the server and the browser. All of the five major web browsers — Internet Explorer, Firefox, Safari, Chrome, and Opera — support client-side decompression of resources that they receive. The server is therefore able to compress JavaScript files using server-dependent capabilities. As part of the server response, a header is included indicating that the file has been compressed using a given format. The browser then looks at the header to determine that the file is compressed, and then decompresses it using the appropriate format. The result is that the amount of bytes transferred over the network is significantly less than the original code size.

For the Apache web server, there are two modules that make HTTP compression easy: mod_gzip (for Apache 1.3.x) and mod_deflate (for Apache 2.0.x). For mod_gzip, you can enable automatic compression of JavaScript files by adding the following line to either your httpd.conf file or a .htaccess file:

#Tell mod_gzip to include any file ending with .js
mod_gzip_item_include         file       .js$

This line tells mod_gzip to compress any file ending with .js that is requested from the browser. Assuming that all of your JavaScript files end with .js, this will compress every request and apply the appropriate headers to indicate that the contents have been compressed. For more information about mod_gzip, visit the project site at www.sourceforge.net/projects/mod-gzip/.

For mod_deflate, you can similarly include a single line to ensure that the JavaScript files are compressed before being sent. Place the following line in either your httpd.conf file or a .htaccess file:

#Tell mod_deflate to include all JavaScript files
AddOutputFilterByType DEFLATE application/x-javascript

Note that this line uses the MIME type of the response to determine whether or not to compress it. Remember that even though text/javascript is used for the type attribute of <script>, JavaScript files are typically served with a MIME type of application/x-javascript. For more information on mod_deflate, visit http://httpd.apache.org/docs/2.0/mod/mod_deflate.html.

Both mod_gzip and mod_deflate result in savings of around 70 percent of the original file size of JavaScript files. This is largely due to the fact that JavaScript files are plain text and can therefore be compressed very efficiently. Decreasing the wire weight of your files decreases the amount of time it takes to transmit to the browser. Keep in mind that there is a slight trade-off, because the server must spend time compressing the files on each request, and the browser must take some time to decompress the files once they arrive. Generally speaking, however, the trade-off is well worth it.

image

Most web servers, both open source and commercial, have some HTTP compression capabilities. Please consult the documentation for your server to determine how to configure compression properly.

SUMMARY

As JavaScript development has matured, best practices have emerged. What once was considered a hobby is now a legitimate profession and, as such, has experienced the type of research into maintainability, performance, and deployment traditionally done for other programming languages.

Maintainability in JavaScript has to do partially with the following code conventions:

  • Code conventions from other languages may be used to determine when to comment and how to indent, but JavaScript requires some special conventions to make up for the loosely typed nature of the language.
  • Since JavaScript must coexist with HTML and CSS, it’s also important to let each wholly define its purpose: JavaScript should define behavior, HTML should define content, and CSS should define appearance.
  • Any mixing of these responsibilities can lead to difficult-to-debug errors and maintenance issues.

As the amount of JavaScript has increased in web applications, performance has become more important. Therefore, you should keep these things in mind:

  • The amount of time it takes JavaScript to execute directly affects the overall performance of a web page, so its importance cannot be dismissed.
  • A lot of the performance recommendations for C-based languages also apply to JavaScript relating to loop performance and using switch statements instead of if.
  • Another important thing to remember is that DOM interactions are expensive, so you should limit the number of DOM operations.

The last step in the process is deployment. Here are some key points discussed in this chapter:

  • To aid in deployment, you should set up a build process that combines JavaScript files into a small number of files (ideally just one).
  • Having a build process also gives you the opportunity to automatically run additional processes and filters on the source code. You can, for example, run a JavaScript verifier to ensure that there are no syntax errors or potential issues with the code.
  • It’s also recommended to use a compressor to get the file as small as possible before deployment.
  • Coupling that with HTTP compression ensures that the JavaScript files are as small as possible and will have the least possible impact on overall page performance.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.233.205