ruk·si

🍦 JavaScript Guide

Updated at 2014-09-24 04:02

This guide contains syntax and tips how to write JavaScript code.

JavaScript is prototype based programming language mainly known for its dominance as client-side web browser language.

There are also other applications of JavaScript e.g. server-side programming with Node.js or JS dialect UnityScript used for game scripting in Unity.

Architecture

Keep all your JavaScript in .js files. JavaScript should not be inline with your HTML. HTML is defines structure, JavaScript defines behaviour. More about this in HTML guide.

Avoid polluting the global scope. JavaScript in browsers doesn't have the concept of namespaces. You can use immediately-invoked functions to provide cleaner public interfaces for libraries.

// bad
function myFunction() {
    console.log('I am global');
}
myFunction();

// good
// Creates window.module if doesn't exist, otherwise extends it.
(function(exports) {
    exports.myFunction = function() {
        console.log('I am not global');
    };
}(window.module = window.module || {}));
module.myFunction(); // shorthand for window.module.myFunction();

Code Layout

Prefer 4 spaces indentation over 2 spaces or tabs. Enforce consistency inside each module or possibly inside each project. Many JavaScript libraries, especially in Node.js, prefer 2 space indentation. I argue that 2 space indentation doesn't stand out enough to be used as presentation for the logical structure.

// good
var Person = function() {
....this.name = 'John Doe';
}

Limit lines to 80 characters. More in master coding guide.

Remove trailing whitespace. Helps with version control merge.

// bad
var name;.

// good
var name;

Start and end files with an empty line. Helps with minification and version control merge.

Always use semicolons ; to end your statements. Even in places where they are syntactically optional. Missing them may cause hard to detect bugs.

// bad
(function() {
    var name = 'Skywalker'
    return name
}());

// good
(function() {
    var name = 'Skywalker';
    return name;
}());

Prefer trailing commas over comma first notation.

// bad
var a = {
    commaFirst: 'trend'
  , isRetarded: true
};

// good
var a = {
    good: 'code',
    isGenerally: 'also pretty, but focus on showing __structure__',
};

Specify one element per line in multiline array and object creation.

// bad
var b = [
    'hello', 'world'
];

// good
var b = [
    'hello',
    'world'
];

Place one space after each control structure keyword. Most notably if, else, for, while and switch.

// bad
if(x > 0)

// good
if (x > 0)

Place else on its own line.

// bad
if (component) {
    component.focus();
} else if (componentToo) {
    componentToo.focus();
} else {
    component = this.createComponent();
}

// good
if (component) {
    component.focus();
}
else if (componentToo) {
    componentToo.focus();
}
else {
    component = this.createComponent();
}

Function keyword is followed by one space.

// bad
var obj = {
    getItem: function () {
        setTimeout(function () {
            console.log('Got it!');
        }, 10);
    }
};

// good
var obj = {
    getItem: function() {
        setTimeout(function() {
            console.log('Got it!');
        }, 10);
    }
};

Don't use spaces between function name and opening parenthesis.

// bad
function getItem () {}

// good
function getItem() {}

Consider adding whitespace to promote readability Use blank lines sparingly to keep vertical height low. Easier to read when used with modesty.

// bad
if(isCondition) doSomething();
while(someCheck(aValue)) iterating++;
for(var i=0;i<100;i++) someIterativeFn();

// good
if (isCondition) {
    doSomething();
}
while ( someCheck(aValue) ) {
    iterating++;
}
var i;
var length = 100;
for (i = 0; i < length; i++){
    someIterativeFn();
}

Use mid-statement line breaks to promote readability. Make incompleteness of multiline statements obvious using illegal symbols like && and + to start the next lines.

// good
if (
    this_one_thing > this_other_thing
    && a_third_thing == a_fourth_thing
    && yet_another && last_one
) {
    // code
}

var someExpression = expression1
    + expression2
    + expression3;

Use braces with all blocks even if they are optional. Helps to show logical structure and requires less hassle when adding rows.

// bad
if (test)
    return false;

if (test) return false;

// good
if (test) {
    return false;
}

Opening braces goes on the same line as the opening keyword. If it fits.

// bad
if (true)
{
    console.log('losing');
}

// good
if (true) {
    console.log('winning');
}

Avoid writing inline JavaScript. It is hard to test properly and you are planning to test your JavaScript, right?

// bad if a lot
<script>
    (function() {
        // ...
    }());
</script>

Object literal property name is followed by one space.

// bad
{
    very:1,
    bad : 2
}

// good
{
    very: 1,
    good: 2
}

Naming

Be consistent with your naming First consider following naming scheme of the frameworks and libraries you use. After that, I personally use the following naming scheme when possible.

var variablesAndPropertiesLowerFirstCamelCase;
var $jqueryWrappedValuesDollarPrefixedLowerFirstCamelCase;
var obj = new ConstructorFunctionInCamelCase();
obj.functionsLowerFirstCamelCase()
obj._privatePropertiesAndFunctionsUnderscorePrefixedLowerFirstCamelCase;
CONSTANTS_THAT_SHOULD_NOT_BE_CHANGED_IN_ALL_CAPS

Use one leading underscore for private properties. With both variables and functions. Then it is impossible to use privates without knowing it.

// bad
this.private = 'Should not be modified but not really secret!';

// good
this._private = 'Should not be modified but not really secret!';

Follow naming guidelines, not natural language. When using abbreviations in names, follow guideline naming rules, not the natural language formatting.

// bad
var dOMDocument = new DOMDocument();
var hTTPError = new HTTPServerError();
var userID = 10;

// good
var domDocument = new DomDocument();
var httpError = new HttpServerError();
var userId = 10;

Avoid one letter and rare names. Use automatic minifier or packer if you are concerned about code size. Easier to read and maintain because of searching.

// bad
var q = function(s){
    return document.querySelectorAll(s);
}
var i,a=[],els=q('#foo');
for(i=0;i< els.length;i++){a.push(els[i]);}

// good
var query = function(selector) {
    return document.querySelectorAll(selector);
}

var idx = 0,
    elements = [],
    matches = query('#foo'),
    length = matches.length;

for ( ; idx < length; idx++ ) {
    elements.push( matches[idx] );
}

Boolean variable names should be yes/no questions e.g. start with is, has, can. Avoid negation in booleans names.

// bad
var errorHappened = false;
var isNotError = false;

// good
var isError = true;

Functions names should start with a verb. Doesn't apply to constructor functions.

// good
var render = function() {};
var countPoints = function() {};
var startObserver = function() {};

Events should be named as shortly and simply as possible. For consistency with established event naming standard.

// bad
var event = 'elementChanged';
var event = 'headerBarWasClicked';

// good
var event = 'change';
var event = 'click';
var event = 'keyup';

Use English. All code and documentation should be written in English.

// bad
asetaKerroin(2);

// good
setMultiplier(2);

Comments

Use /*...*/ for large multiline comments. Use /**...*/for the formal documentation of classes and functions.

// bad
// make() returns a new element
// based on the passed in tag name
function make(tag) {
    // ...
    return element;
}

// good
/**
 * Make
 *
 * Returns a new element based on the passed tag name.
 *
 * @param {String} tag
 * Tag string to use.
 *
 * @return {Object}
 * jQuery wrapped element.
 */
function make(tag) {
    // ...
    return $element;
}

Use // for single line comments. Place single line comments on a new line above the subject of the comment. Prefer placing comment above subject line, but use trailing comments when they are more readable.

// good
var active = true;  // Is this the current tab?

// bad
function getType() {
    console.log('fetching type...');
    // set the default type to 'no type'
    var type = this._type || 'no type';
    return type;
}

// good
function getType() {
    console.log('fetching type...');

    // Set the default type to 'no type'.
    var type = this._type || 'no type';
    return type;
}

Make meaningful comments.

// bad
// Set i to zero.
i = 0;

If you want to make more visual vertical split with a comment, use default multiline comment and fill first line asterisks to the end of line. Should be used with modesty, use different files to split code that is not strictly related.

/**************************************************************************
 * Section Comment
 */

You should document your JavaScript using standard e.g. ScriptDoc. You should at least know following tags:

  • @param: Explain parameters of a function.
  • @return: Explain return value of a function.
  • @exception: Explain reason and type of exception thrown by a function.
  • @example: Explain how function or object can be used.
  • @type: Specifies what data type following variable is.
  • @private: Function or variable should not be accessed outside the parent.

Comment at least constructors, public functions and AJAX responses.

/**
 * Create a new instance of Shape.
 *
 * @example
 *   // Using the move function.
 *   // This example uses the move function to move myLayer to the
 *   // right by 50 pixels.
 *   shape.move('right', 50);
 *
 * @param {Object} name
 * Shapes name, shown in UI.
 * @param {String, Number} [color]
 * Optional hex color value of the shape e.g. #F1E2D3.
 * Defaults to #222222.
 *
 * @return {Shape}
 */
function Shape(name, color) {}

// Document what you expect from an AJAX response.
$.get('/todos/1', function(todo) {
    /**
     * @type {{id:Number}, {text:String}, {status:String}}
     */
    var todo = (todo || {});
});

Variables

Always use var when declaring variables. Otherwise variables go to global scope.

// bad
superPower = new SuperPower();

// good
var superPower = new SuperPower();

Prefer declaring one variable per var statement. If you declare multiple variables in one var, introduce each variable on a separate line. One variable per statement is easier to read, copy, cut and paste.

// bad
var items = getItems(), goSportsTeam = true,
    dragonball = 'z';

// bad
var items = getItems(),
    goSportsTeam = true,
    dragonball = 'z';

// good
var items = getItems();
var goSportsTeam = true;
var dragonball = 'z';

Declare unassigned variables last in multiline vars. Easier to read.

// bad
var i,
    goSportsTeam = true,
    dragonball,
    items = getItems();

// good
var items = getItems(),
    goSportsTeam = true,
    dragonball,
    length,
    i;

Understand variable scoping. Not understanding it may lead to a lot of frustration.

// defines global scope variable
var a = 1;

// uses global variable
function one() {
    alert(a);
}

// uses local variable
function two(a) {
    alert(a);
}

// uses local variable
function three() {
    var a = 3;
    alert(a);
}

// uses local variable
function four() {
    if (true) {
        var a = 4;
    }
    alert(a);
    // => local 4, not global 1
}

// function closure
var five = function() {
    var foo = 6; // `foo` cannot be accessed outside `five`
    return function () { // this inner function is a closure.
        alert(foo);
    }
}

// object properties have their own scope
// prototype properties are accessed as last resort
function Six() {
    this.a = 5;
}
Six.prototype.a = -1;
Six.prototype.b = 8;
var six = new Six();
// six.a === 5
// six.b === 8

Define variables close to where they are used. You don't need to declare all variables at the start of the function.

// bad
function () {
    var name = getName();
    if ( ! arguments.length ) {
        return false;
    }
    doSomething();
    if ( name === 'test' ) {
       return false;
    }
    return true;
}

// good
function () {
    if ( ! arguments.length ) {
        return false;
    }
    doSomething();
    var name = getName();
    if ( name === 'test' ) {
        return false;
    }
    return true;
}

Prefer regular expression literal notion. For consistency.

// bad
var re = new RegExp('\\\\', 'gm');

// good
var re = /\\/gm;

Understand differences between variable types. There are two variable types in JavaScript, primitive and complex.

Primitive Types

Primitives are String, Number, Boolean, null and undefined. With primitive types, you work directly on the value, not with reference to the value.

var foo = 1;
var bar = foo;
bar = 9;
// foo => 1, bar => 9

Avoid wrapper objects for primitive types, use casting. Wrapped primitives don't behave like you would expect.

// bad
var isFull = new Boolean(false);
console.log(typeof isFull);
// 'object';

// good
var isFull = Boolean(false);
console.log(typeof isFull);
// 'boolean'

// hacky, but good acceptable
var isFull = !!(container.length);

Strings

Prefer single quotes over double quotes. For consistency. Write HTML with double quotes.

// bad
var name = "Bob Parr";

// good
var name = 'Bob Parr';

Use string templating engines e.g. Mustache or Handlebars. Helps handling complex strings like server generated HTML.

var data = {
  firstName = 'Tommi',
  lastName = 'Laine';
};

// bad
var html = '<p>'+data.lastName+', '+data.firstName+'</p>';

// good
var html = Mustache.render('<p>{{lastName}}, {{firstName}}</p>', data);

Strings can be written across multiple lines.

// good
var errorMessage = 'This is a super long error that '
    + 'was thrown because of Batman.'
    + 'When you stop to think about '
    + 'how Batman had anything to do '
    + 'with this, you would get nowhere '
    + 'fast.';

Numbers

Use decimals with caution.

var hamburger = 8.20;
var fries = 2.10;
var total = hamburger + fries;
// => 10.299999999999999

var hamburger = 8.20 * 100;
var fries = 2.10 * 100;
total = hamburger + fries;
total = total / 100;
// => 10.3

Complex Types

Complex types are Object, Array and Function. With complex types, you work on a reference to its value, not directly with the value.

var foo = [1, 2];
var bar = foo;
bar[0] = 9;
// foo[0] => 9, bar[0] => 9

All complex types can have properties that can be accessed using dot notation.

var person = { name: 'John Doe' };
alert(person.name);

Objects

Use literal syntax for object construction. Easier to read and less prone to errors.

// bad
var item = new Object();

// good
var item1 = {};
var item2 = {
    name: 'Toothbrush'
};

Prefer unquoted keys. Only quote keys when interpreter complains or if the key naming differs from the normal variable naming. Keeping consistency with way the properties are accessed using dot notation.

// bad
var words = {
    "good": 'code',
    is generally: 'pretty',
    dashed-value: true
};

// good
var words = {
    good: 'code',
    'is generally': 'pretty',
    'dashed-value': true,
    'not_standard': false
};

Avoid using reserved words as keys. Even if you can specify them as strings, accessing them becomes a hassle. Some browsers don't support them e.g. iOS4, IE8, Android 2.1.

// bad
var badman = {
    class: 'superhero',
    default: { john: 'wayne' },
    'delete': function() {...}
};
superman['delete']();

// good
var superman = {
    personClass: 'superhero',
    defaults: { clark: 'kent' },
    remove: function() {...}
};
superman.remove();

Prefer dot notation. Use dot notation . when accessing properties of complex types.

var luke = {jedi: true, age: 28};

// bad
var isJedi = luke['jedi'];

// good
var isJedi = luke.jedi;

Use subscript notation when required. Use subscript notation [] when accessing properties using a variable, property key is an invalid variable or property key differs from normal variable naming.

// good
var luke = {
    jedi: true,
    age: 28
};

function getProp(prop) {
   return luke[prop];
}

var isJedi = getProp('jedi');

Arrays

Use literal syntax for array construction. Easier to read and less error prone.

// bad
var items = new Array();

// good
var items = [];

Use direct assignment over Array.push(). Better performance.

var len = items.length;
var itemsCopy = [];
var i;

// bad
for ( i = 0; i < len; i++ ) {
    itemsCopy.push(items[i])
}

// good
for ( i = 0; i < len; i++ ) {
    itemsCopy[i] = items[i];
}

Prefer plain objects over associative arrays. Associative arrays are buggy.

// bad
var keychain = [];
keychain['skeletonkey'] = 'mastervalue'

// good
var keychain = {};
keychain.skeletonkey = 'mastervalue'

Functions

Functions are first-class objects in JavaScript. They can be passed, they have properties and they can be assigned. On top of that, they can be invoked.

var countDalmatians = function() {
    return 101 ;
};
countDalmatians._functionProcessed = true;
return countDalmatians;

Avoid normal function declarations. Will cause scoping problems and has inconsistent semicolons.

// bad
function bar() {
    return 3;
}

// good
var bar = function() {
    return 3;
};

Use immediately invoked function expressions to encapsulate code.

// define -> invoke -> discard
(function() {
    var a = 100;
}());
// 'a' cannot be accessed from the outside

Never name function parameter arguments or this. Every function has these two variables by default. arguments is an array like collection of passed parameters. this is function context.

// bad
function nope(name, options, arguments) {
   // ...
}

// good
function yup(name, options, parameters) {
    // Number of given parameters
    arguments.length;
    // All arguments as an array.
    var argArray = Array.prototype.slice.call(arguments);
}

Understand how function context (this) works. Variable depends on where and how the function is called. There are four cases.

// CASE 1
// If invoking as a function (without dot), `this` is the global object.
// Function context `this` is `window`.
var functionAtRoot = function() {
  console.log(this);
};
functionAtRoot();
// => Window

// CASE 2
// If invoking as a method of an object (dot-notation), `this` is the owner.
// Function context `this` is the owner object `obby`.
var obby = {
    functionAsMethod: function() {
        console.log(this);
    }
}
obby.functionAsMethod();
// => Object (obby)

// CASE 3
// If invoking function with new-keyword, `this` is a newly created object.
// Function context `this` is new empty object.
function Point() {
    this.y = 10;
    this.x = 10;
    console.log(this);
};
var ppp = new Point();
// => Object with prototype `Point` {x: 10, y: 10}

// CASE 4
// If invoking with using `apply` or `call`, you can specify what `this` is.
// Function context `this` is whatever is specified it to be.
var other = {
    x:10,
    y:10
};
var getX = function() {
    return this.x;
}
getX.call(other);
// => 10
getX.apply(other);
// => 10

Never declare a normal function in a non-function block like if or while. Browsers interpret it differently.

// bad
if ( currentUser ) {
    function test() {
        console.log('Nope.');
    }
}

// good
if ( currentUser ) {
    var test = function test() {
        console.log('Yup.');
    };
}

Name your accessor functions with consistency. Consider isVal() and hasVal() for booleans. Consider getVal() and setVal() for all other types.

// bad
dragon.age();
dragon.age(25);
if ( ! dragon.age() ) {
    return false;
}

// good
dragon.getAge();
dragon.setAge(25);
if ( ! dragon.hasAge() ) {
    return false;
}

Use helper functions inside a single function if the resulting code is simpler.

Return a value or nothing, not both. Don't use void return and return <A_VALUE> in same function. Consider using exceptions or documented error return value.

Use guard statements, returning as early as possible. Less nesting and easier to read.

// bad
var isPercentage = function(val) {
    if (val >= 0) {
        if (val < 100) {
           return true;
        }
        else {
            return false;
        }
    }
    else {
        return false;
    }
}

// good
var isPercentage = function(val) {
    if (val < 0) {
       return false;
    }
    if (val > 100) {
        return false;
    }
    return true;
}

Closures

Understand how closures work in JavaScript. Closure is a function and the stored surrounding context state. In languages without closures, following code would give an error because clickMessycould not be accessed when the click really happens. But JavaScript has closures so it does exist.

var runMe = function() {
    var clickMessy = 'Clicked!';
    $('button').on('click', function() {
       window.alert(clickMessy);
    });
};
runMe();

You can use closures to generate functions with unique context. Allows creating functions that create functions.

function makeAddTo(x) { // creates a closure
    return function(y) { // closure
        return x + y;
    };
}
var addTo5 = makeAddTo(5);
var addTo10 = makeAddTo(10);
console.log(addTo5(2));  // ==> 7
console.log(addTo10(2)); // ==> 12

You can name closure functions. Consider giving closure names for better debug messages.

req.on('end', function onEnd() {
    console.log('winning');
});

Avoid nesting closures. Usually hard to read.

// bad
setTimeout(function() {
    client.connect(function() {
        console.log('losing');
    });
}, 1000);

// good
var afterConnect = function() {
    console.log('winning');
}
setTimeout(function() {
    client.connect(afterConnect);
}, 1000);

Prototypes

JavaScript doesn't have classes. Understand how prototype system works and how it is different from object-oriented thinking.

Basically, each complex type has a prototype that is another object. That prototype object contains functions and properties that are accessible to all other objects with that same prototype. Complex types can also have own properties that are not shared.

var MyClass = function() {
    // Constructor
};
MyClass.prototype; // == Additional functionality for constructed instances
                   //    and reference back to the constructor.
MyClass.__proto__; // == Object with additiona functions, Function.prototype.
var my = new MyClass();
my.__proto__ // == Object with additiona functions, MyClass.prototype.

Avoid modifying native prototypes. It is really rare that you need to make behaviour that is global to all objects or arrays.

Object.prototype;
Array.prototype;

Avoid using __proto__ when accessing object prototype. It is not standardized and it can be missing e.g. IE9+. Use getPrototypeOf with a fallback for IE8.

// good
if ( typeof Object.getPrototypeOf !== 'function' ) {
    if ( typeof 'test'.__proto__ === 'object' ) {
        Object.getPrototypeOf = function(object) {
            return object.__proto__;
        };
    }
    else {
        Object.getPrototypeOf = function(object) {
            // may break if the constructor has been tampered with
            return object.constructor.prototype;
        };
    }
}
assert.equal(Object.getPrototypeOf(user), User.prototype);

Functions double as object constructors. All user-defined functions can be called as a constructor by prepending new to the call.

// Constructor
function User() {
    // ...
}
// Adding a method to the object type.
User.prototype.greet = function() {
    return 'hello';
};
var user = new User();
user.greet();

Construction can be redirected in the constructor itself.

function foo() {
    return new Array(5,6,7);
}
var bar = new foo();
bar.length // => 3

Prototypes can be made to work quite like classes but they are much more.

var Ninja = function(name) {
    this.name = name;
}
var ninja1 = new Ninja('Bob');
var ninja2 = new Ninja('Rose');
ninja1.name; // Bob
ninja2.name; // Rose

// Which can roughly be translated to follow code under
// the syntactical sugar.

var Ninja = function(self, name) {
    self.name = name;
    return self;
}
var ninja1 = {};
ninja1 = Ninja(ninja1, 'Bob');
ninja1.constructor = Ninja;
ninja1.__proto__ = Ninja.prototype;
var ninja2 = {};
ninja2 = Ninja(ninja2, 'Rose');
ninja2.constructor = Ninja;
ninja2.__proto__ = Ninja.prototype;
ninja1.name; // Bob
ninja2.name; // Rose

You can model simple inheritance in JavaScript.

var Mammal = function() {};
Mammal.prototype.breath = function() {
    console.log('brrrh');
}
var Cat = function () {};
Cat.prototype = new Mammal(); // Inherit methods from Mammal.
Cat.prototype.constructor = Cat; // Make 'instanceof' working that they are cats

// Now cats can breath!
var c = new Cat();
console.log(c instanceof String); // false
console.log(c instanceof Mammal); // true
console.log(c instanceof Cat); // true
c.breath(); // brrrh

Consider assigning methods to the prototype rather than overwriting the whole prototype using a plain object. Allows easier inheritance, easier to read and easier to maintain. As downside, take more characters to write.

function Jedi() {
    console.log('jedi constructor');
}

// good, but cannot extend existing prototype with this style.
Jedi.prototype = {
    fight: function fight() {
        console.log('fighting');
    },
    block: function block() {
        console.log('blocking');
    }
};

// good, but takes more space even when minified.
Jedi.prototype.fight = function fight() {
    console.log('fighting');
};
Jedi.prototype.block = function block() {
    console.log('blocking');
};

All user-defined functions automatically have prototype property which in turn has constructor property that refers back to the original function.

assert.equal(user.constructor.prototype, User);

Writing a custom toString() method is okay. Make sure it works and has no side effects.

function Jedi(options) {
    defaultOptions = {
        name: 'no name'
    };
    options = $.extend({}, defaultOptions, options);
    this.name = options.name;
}

Jedi.prototype.getName = function getName() {
    return this.name;
};

Jedi.prototype.toString = function toString() {
    return 'Jedi - ' + this.getName();
};

Hoisting

Understand hoisting. Variable declarations get hoisted to the top of their scope, but their assignment doesn't. Same with anonymous function expressions.

// this doesn't work if there is no 'notDefined' in global
var example = function() {
    console.log(notDefined);
}

// creating a variable declaration after you
// reference the variable will work due to
// variable hoisting
var example = function() {
    console.log(declaredButNotAssigned);
    // => undefined
    var declaredButNotAssigned = true;
}

var example = function() {

    console.log(anonymous);
    // => undefined

    anonymous();
    // => TypeError anonymous is not a function

    var anonymous = function () {
          console.log('anonymous function expression');
    };
}

Normal function declarations hoist their name and functionality. But still avoid using normal function declarations except as constructors.

var example = function () {

    superPower();
    // => Flying

    function superPower() {
        console.log('Flying');
    }

}

Equality

Prefer non-casting equality checks. Use === and !==, not == and !=. == does implicit typecasting to values.

if (1 === '1')   // false
if (1 == '1')    // true

if (0 === '')    // false
if (0 == '')     // true

Here are examples of good equality checks. Optionally use libraries like underscore, lodash or jQuery that provide all these checks e.g. _.isString() or $.isFunction().

// is string
typeof variable === 'string'

// is number
typeof variable === 'number'

// is boolean
typeof variable === 'boolean'

// is object
typeof variable === 'object'

// is null
variable === null

// is variable undefined
typeof variable === 'undefined'

Control Structures

Understand how if works. Conditional expressions are evaluated using ToBoolean method and always follow these rules:

  • Undefined evaluates to false.
  • Null evaluates to false.
  • Numbers evaluate to false on 0 or NaN, otherwise true.
  • Strings evaluate to false on an empty string, otherwise true.
  • Objects evaluate to true.
  • Booleans evaluate to the value of the boolean.

Hide all ugly and frequent checks. Prefer creating custom functions to replace awkward or confusing checks.

// sloppy
if ( typeof name !== 'undefined' ) {
    // ...
}
var $boxes = $('.box-container');
if ( $boxes.length > 0 ) {
    // ...
}

// better
if ( myLib.isSet(name) ) {
    // ...
}
var $boxes = $('.box-container');
if ( myLib.isFound($boxes) ) {
    // ...
}

Name your complex conditions. Any non-trivial conditions should be assigned to descriptive variable.

// bad
if (user.isAdmin() || user.isModerator()) {
    console.log('losing');
}

// good
var isAuthorized = (user.isAdmin() || user.isModerator());
if (isAuthorized) {
    console.log('winning');
}

&& conditionals are allowed. Logical AND operator doesn't evaluate the second expression if the first is false. They are used to much that it's ok to use them.

// bad
if ($('#elem').length) {
    alert("doing something");
}

// good
$('#elem').length && alert("doing something");

Avoid switch. Modern method tracing will blacklist functions with switch statements. If you must use switch, use following style.

var inspect_me = 0;
var result = '';
switch (inspect_me) {
    case 0:
        result = 'zero';
        break;
    case 1:
        result = 'one';
        break;
    default:
        result = 'unknown';
}

Avoid long ternary operators. Consider adding parentheses around the ternary statement part. Easier to read and easier to change.

// bad
longVariable.errorMessage = longVariable.errorType +
(longVariable.errorStrictClassification
    ? ' strict error type'
    : 'loose error type'
);

// good
message = 'Good morning ' + (p.female ? 'Miss ' : 'Mister ') + p.getName();

this.errorMessage = this.errorType + (this.errorType ? ' error' : 'error');

var x = ( isValid(y) ? y : 'default' );

Understand how loops should be used. Use for to iterate arrays and forEach to iterate plain objects. Avoid using for-in without hasOwnProperty filter. It also goes through prototype properties.

// bad
for (var i in man) {
    console.log(i, ':', man[i]);
}

// good
for (var i in man) {
    if (man.hasOwnProperty(i)) {
        console.log(i, ':', man[i]);
    }
}

Optimize your loops.

// Sub-optimal loop
var myarray = [];
for (var i = 0; i < myarray.length; i++) {
    // do something with myarray[i]
}

// Preferred 1
var myarray = [];
var max;
var i;
for (i = 0, max = myarray.length; i < max; i += 1) {
    // ...myarray[i]...
}

// Preferred 2
var i;
var myarray = [];
for (i = myarray.length; i--;) {
    // ...myarray[i]...
}

// Preferred 3
var myarray = [];
var i = myarray.length;
while (i--) {
    // ...myarray[i]...
}

Other Features

Don't write functions to the global scope. Create some kind of structure for your JavaScript, encapsulate functionality in IIFEs and designated global variables. There are multiple ways to do this right and wrong.

// bad
var startApplication = function() {
    // ...
}
startApplication();

// good
var PublicModule = (function($) {
    function PublicModule(options) {
        // ...
    };
    PublicModule.prototype.startApplication = function() {
        // ...
    }
    return PublicModule;
}(window.jQuery));
var module = new PublicModule();
module.startApplication();

Understand how asynchronousity works. JavaScript is not multithreaded. When you make an asynchronous call, the call is added to a task queue. After main scripts have finished running, JavaScript engine will look the task queue for next call to execute.

function logLast() {
    console.log("Last always.");
}
function logStuff() {
    setTimeout(logLast, 0);
    console.log("First");
    return "Second";
}
logStuff();

Avoid timer functions. There are four good usages: to split computationally long functions so others can execute, create asynchronous testing functions, make a function asynchronous with timeout 0 and animations.

// setTimeout, setInterval

Avoid code evaluation functionalities. eval function, new Function(), adding <script> blocks in the document. Code evaluation has few good uses: converting JSON, importing a group of namespaces, metalanguages and function mangling.

Don't use const. IE can't parse it and it's buggy.

// bad
const a = 7;

// good
var a = 7;

Don't use with. Prefer immediate functions and assigning long variable to shorter one. Performance problems, confusing.

// bad
// x can be local property of `foo`
with (foobar) {
    var x = 3;
    return x;
}

// good
(function(f) {
    var f.x = 3;
    return f.x;
}(foobar))

// bad
with (myNamespace.parent.child.person) {
    firstName = 'Jon';
    lastName = 'Smyth';
}

// good
var p = myNamespace.parent.child.person;
p.firstName = 'Jon';
p.lastName = 'Smyth';

Use exceptions only if you get development team wide agreement to use them. catch any errors you can fix. throw errors you cannot fix by yourself but caller might.

try {
    // code that might throw exception
}
catch(exception) {
    // do something to fix the exception
}

Sources