§ Seeds / 2012 / Summer


Summer 2012 an OmniTI publication

Year:

Season:

Javascript Tips for Non-Specialists

Javascript coding is a rather unique skill in the web development world—if you are not experienced with writing Javascript for the browser (especially if you are a coder who works primarily with other languages such as Perl or PHP), it may seem that the language has a mind of its own, refusing to behave in the way a language should. This, combined with the myriad Javascript tutorials on the web which teach sub-par coding practices, makes improving your Javascript skills a daunting task. It's also something that developers usually don't have much time for if they only write Javascript occasionally. All this makes the language appear as an enigma, wrapped in a riddle, nestled inside a sesame seed bun of mystery.

When writing this article, I wanted to provide some tips for non-specialists about ways to make your Javascript faster and easier to maintain, and in some cases, easier on the end user. If you're primarily a Javascript/UI/UX/front-end developer then I hope these concepts are old hat, but for all the other developers out there: this is for you.

Just a quick note: I don't assume jQuery usage throughout this article, but I will reference it from time to time since it's widely used and is a good tool for smoothing out cross-browser differences.

1. Understand Variable Scope and Initialization

Many programmers with backgrounds in other languages have a hard time understanding how variable scope works in Javascript. It's actually very simple; it just seems tricky because it's different from most other languages. There are two things to remember:

Now that we know that there is only the function scope (but that it has access to parent functions' scopes via its scope chain), we need to know how functions are initialized to fully understand why variables behave the way they do. Each function (as well as the global scope) is initialized in the following manner:

  1. Function parameters are declared and values set. Any parameters passed to a function are declared first, with values set.
     
  2. Functions are declared. Next, we declare any sub-functions (remember: they will have the current scope as a higher link in their scope chain).
     
  3. Variables are declared. All variables that are declared anywhere in the current function's scope are now declared (with undefined values), before procedural code is evaluated. This leads to what's commonly known as "variable hoisting," as variables declared at the end of a function are actually available anywhere in the function. (Note: if a variable has the same name as a function, it's not declared twice, but simply references the function until another value is set in step 4.)
     
  4. Variable values are set as procedural code is evaluated. Last, the procedural code in the current scope is evaluated, giving variables their values, etc.

I hope this simplified view gives you insight as to why Javascript variables act the way they do. For a more technical look into how Javascript works, check out the writings of Dmitry Soshnikov on the ECMA-262-3 specification. Now let's move on to some more practical tips.

2. Don't Write Inline Javascript

This one is simple, but I'm including it because I still see it happening. Behavioral Separation is a guiding principle for all front-end developers, and it should be the philosophy of anyone who writes Javascript, even if only occasionally. Javascript is the "behavior layer" of a web site/application and should not be mixed with the HTML "content layer." Thus you should never write:


<button id="my_btn" onclick="doThis();">Submit</button>

Having Javascript inline like this makes maintenance horrendously difficult: there's no single place to look and see all the scripted actions on the page. When a new developer comes onto the project it takes them a long time to search through the markup, find the Javascript, and learn how everything works. And that's not even considering more general maintenance efforts; maintaining inline Javascript simply takes more work than maintaining Javascript which has been kept separate.

If a page needs to initialize page-specific event bindings, it probably needs a page initialization function to be called at the bottom of the page. Thus we only have to look up that initialization function to view all of the Javascript actions and bindings for the entire page. Here is the same button action as above, bound properly from a page initialization function:


<button type="button" id="my_btn">Submit</button>

<!-- These scripts go before the </body> tag. -->
<script type="text/javascript" src="/s/init.js"></script>
<script type="text/javascript">
    initializePage();
    // pretend that these functions are in /s/init.js
    function initializePage() {
        document.getElementById("my_btn")
            .addEventListener("click", doThis);
    }
    function doThis(e) {
        // do cool stuff here
    }
</script>

Just a quick note: the addEventListener() method isn't available in Internet Explorer prior to version 9 (old versions of IE use proprietary methods), so if backwards compatibility is important, you may want to use a library like jQuery to make your cross-browser event bindings easier (requiring just one method call for all browsers including legacy IE).

3. Use Intentional Event Bindings

Since we don't want to use inline event triggers, we have to trigger events with event bindings. With or without libraries like jQuery, it's easy to bind your event handlers with a simple line of code:


// vanilla JS event binding
document.getElementById("my_btn")
    .addEventListener("click", doThis);

// jQuery event binding
$("#my_btn").bind("click", doThis);

Before we bind, though, we need to stop and consider what we intend to make happen. I've seen a lot of "click" event bindings on form submit buttons, when that's not actually what the developer intends. The developer actually wants the event handler to be triggered when the form is submitted, and they mentally associate the submit button with that action. But what if the user is editing a text input and hits "enter?" That would submit the form and completely bypass the submit button binding. Also, what if you want to submit the form via Javascript? It wouldn't make sense to trigger a fake click on the submit button; it's far better to trigger the submission of the form directly. So in this case, since the developer actually intends to capture the form's "submit" event, the submit button shouldn't get a "click" binding. Instead, the form should get a "submit" event binding, which can be triggered in different ways.

4. Use Fast Selectors

There are several different ways to select elements on the page: getElementById, getElementsByClassName, getElementsByTagName, querySelector, and querySelectorAll are the most common (if you're not using jQuery). If you're only dealing with a few elements on the page, then it's not very important how you select your elements. In this case, feel free to use querySelector and querySelectorAll as long as you don't need IE7 compatibility. But if you're manipulating an unknown number of elements that could reach into the hundreds or thousands (or if you need IE7 compatibility), you'll want to be sure to use fast methods, which are usually the oldest and most cross-browser compatible. In this case, I recommend using getElementById, which is supported by all browsers and is the fastest method available (it's an order of magnitude faster than the generic querySelector methods which let you use CSS syntax to select elements).

If you're using jQuery for your selection work, it's still best to use IDs and classes to get the best performance. Many selectors (such as "input[name=first_name]") require string matching on attribute values or other performance-killing techniques, so stick with ids and classnames for your selectors as much as possible…it'll help you stay out of speed traps.

5. Don't Touch the Doc (Unless You Have To)

This is perhaps the most important tip to remember for maximizing your Javascript performance: don't touch the document unless you absolutely have to do so. This is the biggest bottleneck in most Javascript-heavy sites/applications. Your application could be screaming along faster than any app in history, but when you touch the document it's like hitting a traffic jam (especially if you are manipulating hundreds of elements or more).

This is where the DRY mantra, "Don't Repeat Yourself" comes in handy. The best way to minimize document-contact is to save your element references; the first time you use an element, save the element (or jQuery object) in a variable so you'll be able to use it again later without having to touch the document to find it. If you use jQuery, a common pattern for variable naming is to use the "$" character to prefix the names of variables that reference jQuery objects (e.g. $my_button), that way you can tell them apart from other types of variables.

Another important way to minimize contact with the document (thus maximizing performance) is to save attribute values that will be used repeatedly. So instead of this:


var name = document.getElementById("first_name");
if (name.value && ("Chuck" !== name.value)) {
    doThis(name.value);
}

We write this:


var name = document.getElementById("first_name"),
    name_val = name.value;
if (name_val && ("Chuck" !== name_val)) {
    doThis(name_val);
}

Bonus Tip: Equivalency and Truthy/Falsey

Here's an extra tip that will make your life easier when writing "if" statements. When dealing with equivalency tests in a language, you have to know how it tests variables and how to use that to your advantage. In addition to standard equivalency (==, !=) Javascript also has strict equivalency (===, !==). To illustrate the difference, here are some examples:

Although Javascript has actual boolean true and false values, all non-boolean variables have inherent truthy/falsey values. For example, all objects are truthy, so any object, array, or function will always give truthy evaluations (i.e. if (my_obj) ). In addition to 0, null, and undefined being falsey, empty strings are also falsey. So instead of writing if (my_str !== "") or if (my_str.length), you can just write if (my_str). (Note: all non-empty strings are truthy, even "0" despite the fact that "0" == 0 and 0 is falsey!)

In Conclusion

Even if you only write Javascript occasionally, it's still a good practice to be able to write code that will perform well and not place a huge maintenance burden upon future developers. Hopefully, these tips will also help you understand Javascript that others have written, so you'll feel more prepared if you need to debug (or just understand) an existing script. If you don't write Javascript every day—or even every week—you may not need to become a black-belt Javascript ninja, but at least you can feel confident in your code.