JavaScript / jQuery Code Style Commandments

2015-01-19 | #javascript, #styleguide

*Work in progress (still being expanded and linked to other guides)*

Like always, the General Codestyle Commandments (see links) apply before and in addition to the rules described in this article.


I: Thou shalt avoid anything global

Every script you write has to have its own scope. The first thing to do should always be, to start a module with an encapsulating function, preventing anything from your script to bleed into the global scope. Or even better: just use AMD (Asynchronous Module Definition) by default.

// vanilla module

(function(){

// code goes here

})();

// AMD module

define(['jquery'] , function ($){

// define module as function and return it

return function(){};

});

Never ever ever ever initialize a variable without "var"!

If, for some unholy reason, you have to bind anything to the global scope, do this by explicitly referencing "window" when doing so.

Avoid using unbound functions, better bind them to a plain object, to be able to reference them by a true reference instead of a name. Handling functions as objects is much more flexible.


II: Thou shalt stick to common naming patterns

JavaScript is a language demanding a high degree of discipline from its programmers to stay readable. One helping factor here is to implement naming patterns strictly.

JavaScript names are camelCased.

Except for constants, those are UPPERCASED_WITH_UNDERSCORES.

In objects, everything supposedly private starts with a _underscore.

jQuery-enabled variables and instances should always start with a $dollarSign (you can also use this for other framework variables).

Free closures should by marked with a leading f, to identify a fClosure clearly in a context, where it also could be a value.


III: Thou shalt use strict mode

There are excuses for not doing so, but have one if you don't (e.g. needed deprecated functionality).

If you have no good excuse then put 'use strict'; at the start of every script or every module function you produce.


IV: Thou shalt use loading slots purposefully

There are four major types of JavaScript-execution slots:

In header

In body

On "document ready"

On "document load"

Each has its own right to be and time of use. Know them and use them accordingly.

Use header code to execute initialisations, that should be executed as soon as possible and don't need any DOM-information.

Use a script tag as the last body-node for all code working with the DOM.

Use script tags somewhere inside the markup only if that script has to be rendered server-side and put that code right behind the markup the fragement needs.

Use "document ready" always as the standard event for the start of DOM-related program execution.

Use "document load" if you need certain layout dimensions or want to start minor tasks after everything important has been done already.


V: Thou shalt avoid excessive scope nesting

Most JS-developers have heard the term "callback hell". This hell has its origin in the fact that each function carries its outer definition scopes with it, which becomes exponentially more complex the larger the scopes and the deeper the nesting. To manage this, generally very helpful and mighty characteristic, and to enforce some degree of programming discipline one should never nest deeper then two levels coming some kind of root scope.

For example, let's take a common jQuery event handler:

$('button:first').on('click', function(){

var _this_ = this,

currentColor = 'red',

effectTimer = null

;

$(this).next('button').on('click', function(){

var __this__ = this;

$(_this_).css({color : currentColor});

currentColor = (currentColor == 'red') ? 'green' : 'red';

window.clearTimeout(effectTimer);

window.setTimeout(function(){

$(_this_).css({color : currentColor});

$(__this__).remove();

}, 1000);

});

});

These are about 10 "real" lines of functional code. As you can see you have problems of immediately grasping what's happening here exactly. To find out, that this code changes the button text color based on a click on the next button and then changes the color back after one second as well as removing the second button, you'll have to think for a minute.

So ... we should not get much more complicated then this in terms of cross referencing and nesting right? 3 levels. Have a good reason for defining more!


VI: Thou shalt not use things before they are declared

Javascript has the concept of function scopes, where everything declared in a scope is available everywhere, no matter the actual order of declaration. To use something before it's defined is one of those features no one should ever use. Ever. The result, in almost all cases, is highly confusing and unreadable source. This is especially true for variable use.

For functions you could argue that method definitions in objects have no definition order and therefore using var-declared functions the same way should be okay. But it isn't. In JS, var-declared functions are just that: vars. Whether the var value is ordinal or a function simply does not really matter. But that said, of course you don't have to order object methods in the order of use like in ancient pascal times.

Sooooo, in summary.

This is crap:

function fooBar(){

...

i = superFunc();

...

var superFunc = function(){ return i + 3; };

var i = 5;

}

This is okay:

function fooBar(){

var superFunc = function(i){ return i + 3; };

var i = superFunc(5);

...

i = 5;

...

}


VII: Thou shalt be careful, when extending prototypes

JavaScript is a prototype-based language, although mostly not used in that way. That has reasons. Extending a prototype with new methods is a very intransparent way of providing object-based functionality. Essentially you'll have to know prototypes by heart to know which methods are vanilla and which are added individually. The initial appearance of added prototype methods is always that of a native method, which may be very irritating.

Imagine a project with different JS-environments, for example because a part is loaded in an iFrame, without the complete JS code: suddenly array methods that work perfectly in one part of the source may not work in the other part of the source. But why? I'm just using array ain't I?

Even worse, imagine you wrote a shim, providing some function to older browsers, that even looks vanilla. Suddenly standard functionality works in one context, but not in another and that even depends on the used browser!

So, always keep in mind, that expanding prototypes is very implicit, so keep it high-level and consistent. For project functionality, better implement things in utility methods, making added functionality explicit.