JavaScript Code Conventions

30 Mar 2011

I've put together a list of JavaScript code conventions I use for personal and commercial projects.

In addition to this page, I've also created test cases that demonstrate the trickier bits of JavaScript. These are available on GitHub.

Basics

JavaScript files should be stored in .js files.

Indentation should be four spaces. Don't use tabs.

Avoid lines over 80 characters.

End all statements with a semicolon.

The amount of JavaScript on an HTML page should be kept to a minimum. Typically the page could start initialization code:

...
<script>
    $(document).ready(function () {
    myNamespace.initializePage({ color: "blue" });
});
</script>
</body>
</html>

Comments

Add useful comments to bits of code that might require explanation.

var calculateBoxWidth = function (element) {
    // Add a predefined constant to the width of the element to avoid overlap.
    return $(element).width() + myNamespace.SPACING_CONSTANT;
};

Don't use comments where you should use proper naming.

// Names shortened to their first letters. DON'T DO THIS.
// Calculate the box width.
var cbw = function (element) {
    var mbs = 45; // maximum block span
    /* ... */
};

// Correct: use proper naming.
var calculateBoxWidth = function (element) {
    var maximumBlockSpan = 45;
    /* ... */
};

Don't use comments where you should use functions.

// A big hunk of initialization code. DON'T DO THIS.
var initializePage = function () {
    // Load data.
    /* Long block of code... */

    // Create widgets.
    /* Long block of code... */

    // Setup events.
    /* Long block of code... */
};

// Correct: separate the code into functions.
var initializePage = function () {
    loadData();
    createWidgets();
    setupEvents();
};

Names

Variable name should use uppercase, lowercase and numbers. Avoid using special characters.

Variable names should start with a lowercase letter and use camelCase. Don't use underscores.

var meaningOfLife = 42;
var welcomeText = "Hello, World!";
var powerFluxCapacitor = function () {
    /* ... */
};

Use meaningful names that indicate the function of a variable. Naming things is one of the hardest things in programming. Take time to figure out the proper names that represent what you're trying to achieve.

Namespaces

By default, everything in JavaScript ends up in the global namespace. This can cause conflicts between different libraries.

Encapsulate your variables, functions and objects in a custom namespace.

// These variables leak into the global namespace. DON'T DO THIS.
var foo = 12;
var bar = function () {};

// Correct: create one namespace to store everything under.
var myNamespace = {};
myNamespace.foo = 12;
myNamespace.bar = function () {};

Variable declaration

Be careful with declaring variables. Forgetting the var keyword creates an implicit global variable.

JSLint complains if you're initializing variables without the var keyword.

// Leaky global variable. DON'T DO THIS.
var toUpper = function (v) {
    // Since we forgot the "var" keyword, 
    // tmp is now part of the global namespace.
    // DON'T DO THIS.
    tmp = v.toString().toUpperCase();
    return tmp;
};

// Correct: always use the var keyword.
var toUpper = function (v) {
    var tmp = v.toString().toUpperCase();
    return tmp;
};

Functions

Don't use function literals. They clutter up the global namespace. Use the var keyword to define functions.

// Function literals are automatically global. DON'T DO THIS.
function multiply(a, b) {
    return a * b;
}

// Correct: use the var keyword to define a function.
var multiply = function (a, b) {
    return a * b;
};

// Better: put functions under your namespace.
myNamespace.multiply = function (a, b) {
    return a * b;
};

When returning from a function, start the returned object on the same line. Otherwise, the JavaScript parser will think the statement has finished and return without a value.

// Because the return statement is on a line of its own,
// the JavaScript parser considers this line finished and inserts a semicolon. 
// This means that the return statement returns nothing.
// DON'T DO THIS.
var badReturn = function () {
    return
    { success: true  };
};

// Correct: start the statement on the same line as the return keyword.
var goodReturn = function () {
    return {
        success: true
    };
};

Objects

Objects are different in JavaScript than in Python or Java. They don't use class-based inheritance, but prototype-based inheritance. This makes the object model more flexible, but also requires a bit more discipline.

An object can be created using the "new" keyword.

var LifeForm = function (description) {
    this.description = description;
};

var myLifeForm = new LifeForm();

When creating an object, always use the "new" keyword.

This has an effect on the meaning of the "this" variable. If you forget the "new" keyword, "this" will be set to the window, which means everything will end up in the global namespace.

Furthermore, since you don't return anything from the function, the result will be undefined.

To indicate that this is an object that should be instantiated with the "new" keyword, always start the name with an uppercase.

var TestObject = function () {
    this.counter = 42;
};

// Forgetting the new keyword. DON'T DO THIS.
var obj = TestObject();
ok(obj === undefined, "No object has been created.");
ok(counter === 42, "Variable counter has leaked into the global namespace.");

// Correct: use the new keyword to create objects.
var newObj = new TestObject();
ok(typeof newObj === "object", "The object has been created.");
ok(newObj.counter === 42, "The counter is part of the object.");

To create methods on the object, add them to the function prototype.

var Customer = function (name, address) {
    this.name = name;
    this.address = address;
};

Customer.prototype.toJSON = function () {
    return {
        name: this.name,
        address: this.address
    };
};

Inner functions don't have access to the object using this. Instead, their "this" points to the window global. To avoid conflicts, use "that".

var PageSearcher = function (query) {
    this.query = query;
    this.results = [];
};

// The "this" in the inner function points to the window global, 
// leaking variables. DON'T DO THIS.
PageSearcher.prototype.badSearch = function () {
    $.getJSON(PAGE_SEARCH_URL, { 'q': this.query }, function (data) {
        this.results = data.results;
    });
};

// Correct: thanks to closures, "that" points our PageSearcher object.
PageSearcher.prototype.goodSearch = function () {
    var that = this;
    $.getJSON(PAGE_SEARCH_URL, { 'q': this.query }, function (data) {
        that.results = data.results;
    });
};

Scoping

JavaScript does not have block scope. It only has function scope. Variables declared in blocks remain available outside of the block.

To avoid confusion, declare all variables before you use them, at the top of the function.

JSLint will check for this.

// Confusing scoping. DON'T DO THIS.
var loopFunction = function () {
    for (var i = 0; i < 10; i += 1) {
        var tmp = i;
    }
    equals(i, 10, "The i variable is still accessible.");
    equals(tmp, 9, "The tmp variable is still accessible.");
};

// Correct: define all variables at the top of the function.
var betterLoopFunction = function () {
    var i, tmp;
    for (i = 0; i < 10; i += 1) {
        tmp = i;
    }
    equals(i, 10, 
        "The i variable is still accessible, but at least it is explicit.");
    equals(tmp, 9, 
        "The tmp variable is still accessible, but at least it is explicit.");
};

Various

Avoid using ++ and -- since they can create tricky code that is prone to errors.

// ++ can introduce bugs. DON'T DO THIS.
var total = 42;
var counter = 5;
// Tricky code.
var total = total + ++counter;

// Correct: use += and -=.
for (i = 0; i < 10; i += 1) {
    doTheThing();
}

Unit Testing

QUnit makes testing JavaScript easy.

Organize tests into modules. One module = one file.

Here's a complete test example:

module("Circle");

test("Circumference of a circle", function () {
    var c = new Circle({ radius: 5 });
    equals(c.circumference(), c.radius * 2 * Math.PI, 
        "The circumference is the diameter multiplied by Pi.");
});

test("Area of a circle", function () {
    var c = new Circle({ radius: 5 });
    equals(c.area(), c.radius * c.radius * Math.PI, 
        "The area is the square of the radius multiplied by Pi.");
});

Useful resources