Javascript Best Practices


An overview of techniques to use and techniques to avoid

Scott Sauyet / @scott_sauyet

Goals

  • Describe Javascript best practices
  • Show how Javascript is similar to familiar languages
  • Show how Javascript differs from familiar languages
  • Describe certain pitfalls with Javascript and how to avoid them
  • Show how to use Javascript's more unusual features

Javascript is a real language

First and formemost, Javascript must be treated like other important source in a project.

  • Break into reasonable units at the file level
  • Use meaningful variable names
  • Use comments appropriately
  • Use small, readable functions
  • Write unit and functional tests where possible
  • Use code quality checkers (e.g. JSLint or JSHint)

Files

In some languages, it's clear what's supposed to go in a single file. In Java, for instance, one file = one public class. Javascript does not make it so easy. Some of the factors to consider include:

  • Closely related functionality should be in the same file.
  • Files should be kept reasonably small for editing purposes, but minified and combined for download.
  • If you can define modules, no two modules should appear in a single file, although a single module might have to be split across multiple files.
  • If two bits of code depend on the same properties that do not otherwise need to be made public, then they should be in the same file. We'll talk about the techniques for this in the section on closures.

Variables Names

Similar advice as in other languages:

  • Choose meaningful variable names, reserving single-letter names for a very few cases, such as loop iterators i, j, etc.
  • Use appropriate abbreviations, but use them consistently throughout a project.
  • Simple conventions:
    • objects, properties, functions: camelCaseNames
    • constructor functions: PascalCaseNames
    • (pseudo)-constants: ALL_CAPS_NAMES

Comments

Really no different from other languages.

Again, for documentation comments you have more choices than you might have for some languages, but there are many goods ones, and there is no clear consensus about the best one. JSDoc, which languished for years is moving quickly again and starting to look good, though.

(My personal opinion is that most code commenting ends up contributing very little to a project. I find it useful to comment tricky bits that aren't immediately obvious. But if you have those, you should probably come back and fix it anyway.)

Simple, small functions

Remember that functions are much more central to Javascript than to C# or to Java. More on that later.

Just keep things readable.

Unit Testing

There are plenty of good unit test frameworks available, and they're easy to use:

  • QUnit is the most simple to use
  • YUI Test still works, but is getting creaky
  • Jasmine or Cucumber are good if you're interested in BDD-style
  • Mocha seems to be getting the most attention; it's very flexible but still easy to use
  • For testing on multiple browsers simultaneously, tools like JSTestDriver, Buster, or Testacular are all very good

And there are many more

Functional Testing

User interfaces are hard to test.

There is no way around that. But there are tools to make is somewhat easier:

But one good alternative for testing general application flows, so long as you don't need to worry about specific browser bugs, is the headless Webkit implementation, Phantom, hooked together with any of the unit test suites.

Linting

JSLint is a (very intentionally) opinionated piece of software. It comes with the warning JSLint will hurt your feelings. It helps enforce many of the syntactic best practices here. Do not assume it's the last word. Your project can choose to override its default settings without fear. But be sure that you understand what the settings are for, and why you're doing so.

JSHint is a more friendly, more configurable, linter. It might be worth using if you find yourself cursing Douglas Crockford every day.

The most important thing is to make running these part of your regular routine.

What do these two have to do with one another?

JavaJavascript

Exactly as much as these two:

EggEggplant

Javascript is different

Javascript is not an Object-Oriented language.*

Javascript is not an Functional language.

It is a mixture of both, and although it has plenty of warts, it takes many of the best parts of OO and the best parts of Functional and combines them into its own mix.




* "Advocating Object-Oriented Programming is like advocating Pants-Oriented Clothing." – Steve Yegge

In Java

Strategy Pattern

public interface Behavior {
    public int moveCommand();
}

public class AggressiveBehavior implements Behavior{
    public int moveCommand() {
        System.out.println("Attack!");
        return 1;
    }
}

public class DefensiveBehavior implements Behavior{
    public int moveCommand() {
        System.out.println("Run Away!");
        return -1;
    }
}

public class NormalBehavior implements Behavior{
    public int moveCommand() {
        System.out.println("Hang around.");
        return 0;
    }
}

// ...

robot.setBehavior(new NormalBehavior());

In Javascript

Just use functions!

var agressiveBehaviour = function() {
    console.log("Attack!");
    return 1;
};

var defensiveBehavior = function() {
    console.log("Run Away!");
    return -1;
}

var normalBehavior = function() {
    console.log("Hang around.");
    return 0;
}

// ...

robot.setBehavior(normalBehavior);

Simple Things

This section describes some of the simpler things that you might have to do a bit differently in Javascript than in other languages. We will talk about:

  • Javascript Loading
  • Namespacing
  • DOM traversing and manipulation
  • Event delegation
  • Code formatting

Javascript Loading

For performance reasons, it's best to place all your Javascript <SCRIPT> tags at the bottom of the page.

If you do need to include them in the HEAD, it's probably worth running them on page load or document ready.

Consider using an AMD-loader such as Require.js. These tools make it substantially easier to manage dependencies and to keep the global namespace clean. It also makes various more organizational techniqes such as namespacing unnecessary.

Namespacing

Javascript does not (yet) have a native module feature. But plain objects can easily serve that role. If you can't use an AMD-loader, then a technique like this is probably your best bet:

var myApp = myApp || {};
myApp.util = myApp.util || {};

myApp.util.function1 = { ... };
myApp.util.function2 = { ... };

You might need to declare and intialize your namespace at the top of each file that uses it. Alternately, you can write a function that does this for you:

myApp.namespace("util"); // or
namespace("myApp.util");

But then you'll need to ensure that the namespace function is definitely in place before this code runs.

DOM Traversal and Manipulation

While the core language of Javascript has sped up many-fold in recent years and can now be used for some surprisingly performance-intensive work, access to the Document Object Model (DOM) has not kept up.

It's still slow.

Very slow.

Very, very slow.

To mitigate this, your best bet is to minimize the number of queries you make to the DOM, to cache the results of these queries where feasible, to store data in places other than the DOM when possible.

Event Delegation

There is a significant problem with this code *

var links = document.getElementsByTagName("A");
for (var i = 0, len = links.length; i < len; i++) {
    links[i].addEventListener("click", function(event) {
        // do something
    }
}

This creates an event listener for every single <A> element on the page, each with its own copy of an identical function. This is often a huge waste of memory.

Try something like this instead:

document.body.addEventListener("click", function(event) {
    if (event.target && event.target.nodeName === "A") {
        // do something
    }
}

This accomplishes exactly the same thing with a single listener.


*besides the fact that it won't work in IE

Code Formatting

Because Javascript is delivered over the wire in source code format, brevity actually matters. But with compressors often doing much of this job for you, often the only issue left is to not create outrageously long variable names. Other than that, using these formatting conventions will help with legibility and maintainability. But in some cases, they make the difference between code that works and code that doesn't.

  • Indent blocks and object literals 4 spaces
  • Put a space between keywords like `if` and the corresponding `(` to set them apart from method invocation where the parenthesis should be adjacent to the function
  • Do not rely on semicolon insertion, always use semicolons
  • Always use block style with structured statements (if, while, etc.)

Code Formatting, continued

  • Use K & R style, put `{` at the end of the line
  • Declare variables at the beginning of a function
  • Use `===` and `!==` (as opposed to `==` and `!=`) to avoid unintended type coercion
  • Do not allow switch statements to fall through to the next case
  • Do not use the type wrappers (aka new Boolean(false)), it can be confusing and isn't necessary

Javascript Pitfaills

There are some features of Javascript that seem perversely designed to catch the unwary. Some are probably poor design; others simply have to do with the language being substantially different than languages that superficially look much like it.

  • Function scope
  • Global variables
  • Equality operators
  • Semicolon insertion
  • Truthy and falsy values

Function Scope

Javascript does not have block scope. All variables are scoped to the nearest containing function, or are global. In upcoming versions, this will change, and there will be a `let` construct parallel to the `var` one, that does allow for block scoping, but for now, examine this:

function someFunction(param) {
    if (param > 10) {
        var x = 42;
    }
    for (var i = 0; i < 10; i++) {
        localFunction(param, i, x);
    }
    
    // QUESTION: What's in scope here?
    
    function localFunction() {
        // ...
    }
}

Question: What's in scope at the marked spot?

Answer: param, x, i, localFunction, plus anything in the global scope.

Global variables

Probably one of the worst misfeatures of Javascript is how easy it is to accidentally create global variables.

function party() {
    var Event = "Birthday Party", Where = "Chucky Cheese", Time = "1:00";
        Date = "Friday", RSVP = "800-555-1414", GuestOfHonor = "Tommy";
    schedule(Event, Where, Time, Date, RSVP, GuestOfHonor);
}

Do you see the bug?

It's the first semicolon. Programmers generally know that global variables are a bad idea. In Javascript, if you use a var statement in a global context, or forget to use a var statement altogether, your variable is now a global. It can clobber almost any other global out there, including built-in things such as the Date constructor.

Moral: be careful of your var statements.

Another moral: Think carefully about using commas to separate your var statements. It can be fragile.

Equality Operators

Javascript offers two styles of equality/inequality testing. One of them (`==` / `!=`) coerces the objects to be of similar types and then checks for equality. The other (`===` / `!==`) does no coercion, comparing both type and value.

Although there are theoretically some times when the coercion could be helpful and exactly what you want, and there are times when you know for certain that the types under comparison are the same, and so the first style would work, you will run into problems with some linters, and will likely run into maintenance problems as other developors may not recognize what you know.

The recommendation is that if you need the type coercion, perform it manually, and test with `===` or `!==`:

// Don't do this to force string `amt` to a number:
if (amt != 0) { /* ... */}

// Instead, be explicit:
if (Number(amt) !== 0) { /* ... */}

Automatic Semicolon Insertion

It sounds like a helpful idea: if you forget a semicolon, the interpreter can generally figure out that you meant to include one.

However…

var expand = function(rect) {
    return
        {
            height: rect.height * 2,
            width: rect.width * 2
        };
};
  
var myRect = {height: 10, width: 20};
alert(expand(myRect).width);

Question: What does this return?

Automatic Semicolon Insertion

Question: What does this return?

var expand = function(rect) {
    return
        {
            height: rect.height * 2,
            width: rect.width * 2
        };
};
  
var myRect = {height: 10, width: 20};
alert(expand(myRect).width);

Answer: Nothing. It's a syntax error. The `return` on it's own line is considered a statement of its own, and a virtual semicolon is inserted. The `{` opens a block, and `height:` is a label, but then because of the comma, the statement is not complete before another label-like token, `width:` is encountered, and we hit an error.

Had the `{` been on the same line as the `return`, we would have had no issues. This is the reason for insisting on K & R-style brace placement. But this whole thing is a reason to be wary of Automatic Semicolon Inserion.

Truthy and Falsy Values

When testing for truthiness, such as in an `if` test, Javascript has a number of distinct values that report as false: 0 (both positive and negative zero [don't ask!]), NaN, "" (empty string), false, null, and undefined.

Anything else is truthy, including some surprising ones such as: "0", {} (empty object), and [] (empty array).

So instead of something like this to test an object with a string property:

if (obj !== null && obj !== undefined && obj.prop !== null & 
    obj.prop !== undefined && obj.prop != "") { /* ... */ }

You can simply do something like this:

if (obj && obj.prop) { /* ... */ }

Most important Javascript Features

In Object-Oriented languages, it's clearly Objects (and Classes) that are the central features.

Javascript has three central features that work closely together:

  • Objects, with an unusual inheritance mechanism
  • First-class functions
  • Mutable lexical closures

To use the language fully, we must be able to take advantage of how all of these work and how they interact.

Objects

Using Objects

First of all, start simple. It might always be enough to do something like this:

var dog = {
    name: "Rover"
    speak: function() {alert(this.name + " says woof";}
};

But if you have many similar objects, you might want to reuse the speak function. That's where prototypes come in.

Using Objects directly through Prototypes

If you need to start sharing prototypes, you can do something like this:

var dogPrototype = {
    speak: function() {alert(this.name + " says woof");}
};

var dog1 = Object.create(dogPrototype);
dog1.name = "Rover";
dog1.speak();

// or 

var dog2 = Object.create(dogPrototype, {name: {value: "Fido"}});
dog2.speak();

Note that this technique requires a shim to work in older versions of IE

Building Objects with Constructor Functions

You can also do something that feels more familiar:

var Dog = function(name) {
    this.name = name;
};
Dog.prototype.speak = function() {alert(this.name + " says woof");}

var dog1 = new Dog("Rover");
dog1.speak();

While not exactly the same, this has a very similar effect as the previous sample.

Prototypes

Developers should become familiar with JavaScript's prototypal inheritance.

Unlike Java or C#, JavaScript does not have classes.

Prototypal inheritance is powerful but misunderstood — essentially, JavaScript objects contain a pointer to the parent object. On property access, the Javascript engine walks "up the tree" to find the requested property (arriving at undefined should the property not exist anywhere in the chain).

Functions

Using Functions

Functions are first-class citizens of the Javascript world, at least as important as objects, and often more important.

  • Functions can be assigned to variables
  • Functions can be supplied as parameters to function calls
  • Functions can be returned from other functions
  • Functions are objects, and can be assigned properties
  • Functions can even be dynamically constructed from parts

Using functions in these ways is essential to properly understanding Javascript.

Example: In need of refactoring

var isPreexisting = function(vehId) {
    if (typeof vehId !== "string") {
        vehId = (vehId && vehId["@id"]) || "Unknown";
    }
    return (Model.get("actionCode", {vehId: vehId}) !== "A");
};

var isNonOwned = function(vehId) {
    if (typeof vehId !== "string") {
        vehId = (vehId && vehId["@id"]) || "Unknown";
    }
    return (Model.get("vehicleType", {vehId: vehId}) === "NO");
};

var isCompOnly = function(pkgId, vehId) {
    if (typeof vehId !== "string") {
        vehId = (vehId && vehId["@id"]) || "Unknown";
    }
    return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
};

All these Strategic UI functions should accept either a vehicleId as a String or a vehicle object with a string property named "@id". But there is too much repetetive code in them.

Example: Familiar Refactoring

var getRealVehicleId = function(vehId) {
    return (typeof vehId === "string") ? vehId : 
           (vehId && vehId["@id"]) || "Unknown";
};
                      

var isPreexisting = function(vehId) {
    vehId = getRealVehicleId(vehId);
    return (Model.get("actionCode", {vehId: vehId}) !== "A");
};

var isNonOwned = function(vehId) {
    vehId = getRealVehicleId(vehId);
    return (Model.get("vehicleType", {vehId: vehId}) === "NO");
};

var isCompOnly = function(pkgId, vehId) {
    vehId = getRealVehicleId(vehId);
    return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
};

This style refactoring should look familiar. There is nothing wrong with it. But Javascript offers alternative techniques that can be more powerful, and, once learned, more intuitive

Example: Functional Refactoring

var withVehId = paramReplacer(function(vehId) {
    return (typeof vehId === "string") ? vehId :
           (vehId && vehId["@id"]) || "Unknown";
});

var isPreexisting = withVehId(function(vehId) {
    return (Model.get("actionCode", {vehId: vehId}) !== "A");
});

var isNonOwned = withVehId(function(vehId) {
    return (Model.get("vehicleType", {vehId: vehId}) === "NO");
});

var isCompOnly = withVehId(function(pkgId, vehId) {
    return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
}, 1);

Instead of adding code inside our simple functions, we wrap them up inside another function that will do the translations of the parameter before calling our function. But this depends on paramReplacer. Let's look at that.

paramReplacer

var paramReplacer = function (adjuster) {
    return function(fn, index) {
        index = (typeof index === "number") ? index : 0;
        return function() {
            if (arguments.length > index) {
                return fn.apply(this, [].splice.call(arguments, 
                            index, 1, adjuster(arguments[index]));
            }
            return fn.apply(this, arguments);
        };
    };
};

Okay, that's a mouthful. But this sort of technique can be very powerful. This is a function that returns functions that return functions. Using Javascript this way in a few key places can significantly simplify the rest of your code.

var withVehId = paramReplacer(function(vehId) {
    return (typeof vehId === "string") ? vehId :
           (vehId && vehId["@id"]) || "Unknown";
});

var isCompOnly = withVehId(function(pkgId, vehId) {
    return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
}, 1);

IIFEs

A common technique seen in Javascript program is the Immediately Invoked Function Expression (IIFE), which looks like this:

(function() {
    // something here
}());

This creates an anonymous function, and immediately invokes it. Note that you can use parameters as well, as in this common technique to ensure that the `$` variable is assigned to jQuery inside your function:

(function($) {
    // here $ is always a reference to jQuery
}(jQuery));

Closures

Quick Question

Is there something surprising about this?:

function createHandler(key) {
    var count;
    return function() {
        alert("You called handler " + key + " " + (++count) + "time(s)");
    }
}
var myFunc1 = createHandler("abc");
var myFunc2 = createHandler("def");

myFunc1(); // alerts "You called handler abc 1 time(s)"
myFunc1(); // alerts "You called handler abc 2 time(s)"
myFunc2(); // alerts "You called handler def 1 time(s)"

Quick Question

There is something that might be surprising about this:

function createHandler(key) {
    var count;
    return function() {
        alert("You called handler " + key + " " + (++count) + "time(s)");
    }
}
var myFunc1 = createHandler("abc");
var myFunc2 = createHandler("def");

myFunc1(); // alerts "You called handler abc 1 time(s)"
myFunc1(); // alerts "You called handler abc 2 time(s)"
myFunc2(); // alerts "You called handler def 1 time(s)"

When the createClickHandler function returns a new function, it seems as though key and count should go out of scope. Instead JavaScript creates a closure of all variables in scope. myFunc has access to (and can change) those values when it's called.

A closure is simply a scope that is kept around after you would otherwise expect it to be gone. A function defined with access to that scope retains access.

Notes on Closures

  • Closures can be used to simulate private object properties in Javascript. Douglas Crockford published this in 2001
  • There was a period where creating closures was frowned on for performance reasons. But it's gotten to the point where looking up items from a closure is faster than looking it up from a global variable.
  • Closures are not difficult. The difficulty is in adjusting the mindset from other languages.
  • There are gotchas, especially having to do with loop variables.
  • The first few links on a Google Search for "javascript closures" are actually pretty good references. Richard Cornford's FAQ entry is the most complete, but the least accessible.

Simple Closure Example

An example from Webreference shows how we can extend the document.createElement function to accept additional parameters such as id and classname:

document.createElement = (function (fn) {
    return function (type, id, className) {
        var elem = fn.call(document, type);
        if (id) elem.id = id;
        if (className) elem.className = className;
        return elem;
    };
})(document.createElement);

Notice the use of an IIFE here. They are commonly used to create closures.

Note: Replacing the native DOM method might not be a great idea, by the way.

Closure data is Mutable

var nextId = (function() {
    var ctr = 0;
    return function() {return ctr++;};
}());

Note that this although the ctr variable is not directly visible outside that function, it stays alive, and the function can modify its value. This differs from some languages which offer closure-access to static variables, but do not allow you to change them.

Again, this uses a simple IIFE.

Closure Loop Gotcha

var rows = myTable.getElementsByTagName("TR");
for (var i = 0; i < rows.length; i++) {
    rows[i].addEventListener("click", function() {
        alert("Clicked row " + (i + 1)};
    });
}

This looks as though it will alert the number of the current row on a click.

In fact, though, it alerts the final value of i (one more than the number of rows.)

One solution: add an IIFE

var rows = myTable.getElementsByTagName("TR");
for (var i = 0; i < rows.length; i++) {
    rows[i].addEventListener("click", (function(row) {return function() {
        alert("Clicked row " + row)};
    }(i + 1)));
}

Promises: escaping callback hell

There is much complex code when asynchronous actions are involved. Often we have deeply nested callbacks to handle the results of many asynchronous calls. The code becomes ugly and brittle. To fix this, some libraries have introduced a Promises API. A promise is not exactly a value. It's an agreement to call certain registered functions with a value when that value is ready or certain other ones if an error prevents determination of that value. jQuery implemented its version of this API in the 1.5.x branch. And we've started to use it in various places in our application.

Promise Definition

There is still some confusion over the various proposed specs for Promises, but at the base is a very simple function, then(callback, errback). An object that has such a function and handles these input parameters properly is a Promise. An object that implements this can be returned from a function, and that function does not have to call your callback functions directly, leaving this responsibility entirely in the hands of the caller.

Promise Properties

You can attach a success callback and/or an error callback at any point in the lifecycle of the asynchronous operation. If the operation hasn't finished it will add these to a queue to process later. If it's already finished, the success or failure callback will be handled immediately.

Another function, when allows us to abstract out the difference between synchronous and asynchronous behaviors. when is supplied an object, which might or might not be a Promise, and returns a Promise whose success and failure handlers will be called when the input promise is resolved or rejected.

Promise Example

var pause = function(millis) {
    var dfd = jQuery.Deferred();
    setTimeout(function() {
        dfd.resolve(new Date());
    }, millis || 1000);
    return dfd.promise();

}

alert("The time is now " + new Date());
pause(5000).then(function(now) {
    alert("Five seconds later the date is " + now);
});

Often it's easier to use this version, which does the same thing:

var pause = function(millis) {
    return jQuery.Deferred(function(dfd) {
        setTimeout(function() {
            dfd.resolve(new Date());
        }, millis || 1000);
    }).promise();
}

Using Features Appropriately

The interaction of simple prototype-based objects, first-class functions, and mutable closures gives us many more ways to structure our code than do simple OO languages. So sometimes additional guidelines are needed.

  • To achieve data hiding, closures are really your only choice. Without closures, your data is public.
  • To achieve reusability, various techniques with functions are available, from prototypal methods to pure functional programming.
  • To minimize memory footprint, make strong use of objects and prototypes.
  • To write the cleanest code, consider straightforward functional approaches.
  • To avoid littering your entire application with small bits of state, use a few well-chosen closures.

Make your own decisions

Note that this advice is all conditional. There is no single best style of Javascript application. There are a lot of powerful tools available; you will have to choose which ones are most important to your project.

Questions?