JavaScript Style Guide
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
README.md

README.md

JavaScript Style Guide - Abstract

I see a lot of new or existing JavaScript developers are not coding in the proper way in my office. I always see things which can be improved on their codebase, but I can't help them much because I moved to the most busiest Ruby team. I understand that JavaScript is the most misunderstood language, but many people is able to catch it up fairly well.

Two years before I left JavaScript, I was intended to do presentation about what are the most common mistakes in the my office. However, I failed to do it because of some reasons. A few months ago I see there are a Ruby and Rails Style Guide. I think I should do one on JavaScript Style Guide.

There are many sources I've combined into the below documents. You could see the link for more details in each topic and in the References section. The main ones are from Google Javascript Style Guide, JavaScript Patterns from Addy Osmani, JavaScript Object-Oriented Programming from Mozilla. Here, I took most of the good parts and write up in a bit more details to clarify.

Anyway, welcome your feedbacks. (The work is in progress.)

Table of Contents

JavaScript Language Rules

var

  • Always declare variables with var.
  • If you forget to declare with var keyword, that variable will be placed in the global context. Sometimes, it's hard to debug the problems.

    function test() {
      a = "hello";
    }
    alert(a);

Constants

  • Use NAMES_LIKE_THIS for constants.
  • As for the const keyword, Internet Explorer doesn't parse it, so don't use it.

    goog.example.SECONDS_IN_A_MINUTE = 60;

Semicolons

Function declarations within blocks

  • Never declare function within blocks. Do not do this:

    if (x) {
      function foo() {}
    }
  • It is not part of ECMAScript. Do this instead:

    if (x) {
      var foo = function () {}
    }

Wrapper objects for primitive types

  • Don't use wrapper objects for primitive types.

    var x = new Boolean(false);
    if (x) {
      alert('hi');  // Shows 'hi'.
    }
  • However, it is very useful for casting things to number, string and boolean.

    var x = Boolean(0);
    if (x) {
      alert('hi');  // This will never be alerted.
    }
    typeof Boolean(0) == 'boolean';
    typeof new Boolean(0) == 'object';

Method definitions

  • The preferred style of declaring method definition is:

    Foo.prototype.bar = function () {
      /* ... */
    };

eval()

  • Use eval only for parsing ajax response.

    var userOnline = false;
    var user = 'nusrat';
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open('GET', 'http://chat.google.com/isUserOnline?user=' + user, false);
    xmlhttp.send('');
    // Server returns:
    // userOnline = true;
    if (xmlhttp.status == 200) {
      eval(xmlhttp.responseText);
    }

with() {}

  • Local variables may collide with the property's object of the with statement. It would be a good idea not to use the with statement because it may change the meaning of your program.

    with (foo) {
      var x = 3;
      return x;
    }

    Instead of this:

    with (ooo.eee.oo.ah_ah.ting.tang.walla.walla) {
      bing = true;
      bang = true;
    }

    you could do this:

    var o = ooo.eee.oo.ah_ah.ting.tang.walla.walla;
    o.bing = true;
    o.bang = true;

this

  • The semantics of this can be tricky. At times, it refers to:

    • the global object (in most places)

      function foo() {
        this.bar = "hello"
      };
      
      foo();
      alert(bar);  // Shows 'hello'.
    • the scope of the caller (in eval)

      foo = {
        name: "hi",
        execute: function () {
          eval("alert(this.name)");
        }
      }
      
      foo.execute();  // Show 'hi'.
    • a node in the DOM tree

      <div onclick="alert(this.innerHTML)">Hello World</div>
      
      // Show 'Hello World' when click that div.
    • a newly created object (in a constructor)

      var Foo = function(name) {
        this.name = name;  // this refers to the new object
      };
    • some other object (if function was call()ed or apply()ed)

      var x = 10;
      var o = { x: 15 };
      
      function f() {
        alert(this.x);
      }
      
      f();  // Show '10'
      f.call(o);  // Show '15'
  • It's often to get it wrong, limit its use to those places where it is required:

    • in constructors
    • in methods of objects (including in the creation of closures)

for-in loop

  • Only for iterating over keys in an object/map/hash, NOT in an Array.
  • for-in does not loop from 0 to length - 1 but over all the present keys in the object and its prototype chain.

    function printArray(arr) {
      for (var key in arr) {
        print(arr[key]);
      }
    }
    
    printArray([0,1,2,3]);  // This works.
    
    var a = new Array(10);
    printArray(a);  // This is wrong.
    
    a = document.getElementsByTagName('*');
    printArray(a);  // This is wrong.
    
    a = [0,1,2,3];
    a.buhu = 'wine';
    printArray(a);  // This is wrong again.
    
    a = new Array;
    a[3] = 3;
    printArray(a);  // This is wrong again.
  • Always use normal for loops when using arrays.

    function printArray(arr) {
      for (var i = 0, l = arr.length; i < l; i++) {
        print(arr[i]);
      }
    }

Associative Arrays

  • Never use Array as a map/hash/associative array (more precisely you are not allowed to use non number indexes for arrays).
  • If you need a map/hash use Object instead of Array in these cases because the features that you want are actually features of Object and not of Array.

Multiline string literals

  • Do not do this:

    var myString = 'A rather long string of English text, an error message \
                    actually that just keeps going and going -- an error \
                    message to make the Energizer bunny blush (right through \
                    those Schwarzenegger shades)! Where was I? Oh yes, \
                    you\'ve got an error and all the extraneous whitespace is \
                    just gravy.  Have a nice day.';
  • Use string concatenation instead:

    var myString = 'A rather long string of English text, an error message ' +
        'actually that just keeps going and going -- an error ' +
        'message to make the Energizer bunny blush (right through ' +
        'those Schwarzenegger shades)! Where was I? Oh yes, ' +
        'you\'ve got an error and all the extraneous whitespace is ' +
        'just gravy.  Have a nice day.';

Array and Object literals

  • Use Array and Object literals instead.
  • Array constructors are error-prone due to their arguments.

    // Length is 3.
    var a1 = new Array(x1, x2, x3);
    
    // Length is 2.
    var a2 = new Array(x1, x2);
    
    // If x1 is a number and it is a natural number the length will be x1.
    // If x1 is a number but not a natural number this will throw an exception.
    // Otherwise the array will have one element with x1 as its value.
    var a3 = new Array(x1);
    
    // Length is 0.
    var a4 = new Array();
  • To avoid these kinds of weird cases, always use the more readable array literal.

    var a = [x1, x2, x3];
    var a2 = [x1, x2];
    var a3 = [x1];
    var a4 = [];
  • Object constructors don't have the same problems, but for readability and consistency object literals should be used.

    var o = new Object();
    
    var o2 = new Object();
    o2.a = 0;
    o2.b = 1;
    o2.c = 2;
    o2['strange key'] = 3;

    Should be written as:

    var o = {};
    
    var o2 = {
      a: 0,
      b: 1,
      c: 2,
      'strange key': 3
    };

Modifying prototypes of builtin objects

  • Modifying builtins like Object.prototype and Array.prototype are strictly forbidden.
  • Modifying other builtins like Function.prototype is less dangerous but still leads to hard to debug issues in production and should be avoided.

Data Access

  • Based on the book "High Performance JavaScript":

    • Literal value and local variable access tend to be faster than array item and object member access.
    • Always store out-of-scope values in local variables if used more than once because older browsers take much larger amount of time taken to access values.
    function initUI() {
      var bd = document.body,
          links = document.getElementsByTagName("a"),
          i= 0,
          len = links.length;
    
      while (i < len) {
        update(links[i++]);
      }
    
      document.getElementById("go-btn").onclick = function () {
        start();
      };
    
      bd.className = "active";
    }

    It could be written in this way by declaring local variables:

    function initUI() {
      var doc = document,
          bd = doc.body,
          links = doc.getElementsByTagName("a"),
          i = 0,
          len = links.length;
    
      while(i < len) {
        update(links[i++]);
      }
    
      doc.getElementById("go-btn").onclick = function () {
        start();
      };
    
      bd.className = "active";
    }
    • Older browsers, IE and FF 3.5, incur a performance penalty with each additional step into the prototype chain.
    • The nested members such as "window.location.href" causes the javascript engine to go through the object member resolution process two times (the number of dots).
    function toggle(element) {
      if (YAHOO.util.Dom.hasClass(element, "selected")) {
        YAHOO.util.Dom.removeClass(element, "selected");
        return false;
      } else {
        YAHOO.util.Dom.addClass(element, "selected");
        return true;
      }
    }

    It would be written in this way:

    function toggle(element) {
      var Dom = YAHOO.util.Dom;
      if (Dom.hasClass(element, "selected")) {
        Dom.removeClass(element, "selected");
        return false;
      } else {
        Dom.addClass(element, "selected");
        return true;
      }
    }

Closure

  • A closure function requires more memory overhead in a script than a nonclosure function because it keeps a pointer to its enclosing scope. As a result, it might be a problem, circular reference and thus create memory leak, on IE 6.
  • Although it might not create memory leak on other browsers, it incurrs a performance penalty with each access of out-of-scope identifiers.

    function foo(element, a, b) {
    
      /* This event handler is a closure, can access a and b.
         It keeps a reference to element, a, and b although it never uses element.
         element also keeps a reference to the closure as well.
      */
      element.onclick = function() { /* uses a and b */ };
    }

    It could be written in this way:

    function foo(element, a, b) {
      element.onclick = bar(a, b);
    }
    
    function bar(a, b) {
      return function() { /* uses a and b */ }
    }

JavaScript Style Rules

Naming

  • In general, use:

    • functionNamesLikeThis,
    • variableNamesLikeThis,
    • ClassNamesLikeThis,
    • EnumNamesLikeThis,
    • methodNamesLikeThis,
    • SYMBOLIC_CONSTANTS_LIKE_THIS,
    • onEventHandlerFunction
  • Properties and methods

    • Private members should be named with a trailing or prefixed underscore.
    • Like public members, protected members should be named without underscore.

      // File 1.
      
      /** @constructor */
        AA_PublicClass = function () {
      };
      
      /** @private */
      AA_PublicClass.staticPrivateProp_ = 1;
      
      /** @private */
      AA_PublicClass.prototype.privateProp_ = 2;
      
      /** @protected */
      AA_PublicClass.staticProtectedProp = 31;
      
      /** @protected */
      AA_PublicClass.prototype.protectedProp = 4;
      
      // File 2.
      
      /**
      * @return {number} The number of ducks we've arranged in a row.
      */
      AA_PublicClass.prototype.method = function () {
        // Legal accesses of these two properties.
        return this.privateProp_ + AA_PublicClass.staticPrivateProp_;
      };
      
      // File 3.
      
      /**
      * @constructor
      * @extends {AA_PublicClass}
      */
      AA_SubClass = function () {
        // Legal access of a protected static property.
        AA_PublicClass.staticProtectedProp = this.method();
      };
      goog.inherits(AA_SubClass, AA_PublicClass);
      
      /**
      * @return {number} The number of ducks we've arranged in a row.
      */
      AA_SubClass.prototype.method = function () {
        // Legal access of a protected instance property.
        return this.protectedProp;
      };
  • Namespacing

    • Use namespaces for global code to prevent collisions during code integration from two projects.
    • Always prefix with a unique namespace name.

      var sloth = {};
      
      sloth.sleep = function () {
        ...
      };
    • Alias long type names to improve readability Use local aliases, the last part of the type, for fully-qualified types if doing so improves readability.

      /**
      * @constructor
      */
      some.long.namespace.MyClass = function () {
      };
      
      /**
      * @param {some.long.namespace.MyClass} a
      */
      some.long.namespace.MyClass.staticHelper = function(a) {
        ...
      };

      Never create aliases in the global scope. Use them only in function blocks.

      myapp.main = function () {
        var MyClass = some.long.namespace.MyClass;
        var staticHelper = some.long.namespace.MyClass.staticHelper;
        staticHelper(new MyClass());
      };

      Do not alias namespaces.

      myapp.main = function () {
        var namespace = some.long.namespace;
        namespace.MyClass.staticHelper(new namespace.MyClass());
      };
  • Filenames should be all lowercase. Filenames should end in .js, and should contain no punctuation except for - or _ (prefer - to _).

Code formatting

  • Always use braces on if/else even though it has only statement.
  • Always start your curly braces on the same line.

    if (something) {
      // ...
    } else {
      // ...
    }
  • Single-line array and object initializers are allowed when they fit on a line:

    var arr = [1, 2, 3];  // No space around [].
    var obj = {a: 1, b: 2, c: 3};  // No space around {}.
  • Multiline array initializers and object initializers are indented 2 spaces.

    // Object initializer.
    var inset = {
      top: 10,
      right: 20,
      bottom: 15,
      left: 12
    };
    
    // Array initializer.
    this.rows_ = [
      '"Slartibartfast" <fjordmaster@magrathea.com>',
      '"Zaphod Beeblebrox" <theprez@universe.gov>',
      '"Ford Prefect" <ford@theguide.com>',
      '"Arthur Dent" <has.no.tea@gmail.com>',
      '"Marvin the Paranoid Android" <marv@googlemail.com>',
      'the.mice@magrathea.com'
    ];
    
    // Used in a method call.
    goog.dom.createDom(goog.dom.TagName.DIV, {
      id: 'foo',
      className: 'some-css-class',
      style: 'display:none'
    }, 'Hello, world!');
  • Always prefer non-aligned initialization. For example:

    CORRECT_Object.prototype = {
      a: 0,
      b: 1,
      lengthyName: 2
    };

    Not like this:

    WRONG_Object.prototype = {
      a          : 0,
      b          : 1,
      lengthyName: 2
    };
  • Whitespace

    • No tabs. Indent blocks with two spaces.
    • Spaces after commas and semicolons.
    • Separate binary operators with spaces.
    • Space after keywords (if, for, etc).
    • No trailing space on each line.
    • Spaces are NOT necessary inside brackets.

      myFunction(a, b)
    • Use newlines to group logically related pieces of code or to seperate between block definitions.

      doSomethingTo(x);
      doSomethingElseTo(x);
      andThen(x);
      
      nowDoSomethingWith(y);
      
      andNowWith(z);
  • Indentation

    // Four-space, wrap at 80.  Works with very long function names, survives
    // renaming without reindenting, low on space.
    goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
        veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
        tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
      // ...
    };
    
    // Four-space, one argument per line.  Works with long function names,
    // survives renaming, and emphasizes each argument.
    goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
        veryDescriptiveArgumentNumberOne,
        veryDescriptiveArgumentTwo,
        tableModelEventHandlerProxy,
        artichokeDescriptorAdapterIterator) {
      // ...
    };
    
    // Parenthesis-aligned indentation, wrap at 80.  Visually groups arguments,
    // low on space.
    function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
                 tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
      // ...
    }
    
    // Parenthesis-aligned, one argument per line.  Visually groups and
    // emphasizes each individual argument.
    function bar(veryDescriptiveArgumentNumberOne,
                 veryDescriptiveArgumentTwo,
                 tableModelEventHandlerProxy,
                 artichokeDescriptorAdapterIterator) {
      // ...
    }
    prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
      if (a1.equals(a2)) {
        someOtherLongFunctionName(a1);
      } else {
        andNowForSomethingCompletelyDifferent(a2.parrot);
      }
    });
    
    var names = prefix.something.myExcellentMapFunction(
        verboselyNamedCollectionOfItems,
        function(item) {
          return item.name;
        });
    someWonderfulHtml = '' +
                        getEvenMoreHtml(someReallyInterestingValues, moreValues,
                                        evenMoreParams, 'a duck', true, 72,
                                        slightlyMoreMonkeys(0xfff)) +
                        '';
    
    thisIsAVeryLongVariableName =
        hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine();
    
    thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() +
        thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore();
    
    someValue = this.foo(
        shortArg,
        'Some really long string arg - this is a pretty common case, actually.',
        shorty2,
        this.bar());
    
    var IOService = Components.classes["@mozilla.org/network/io-service;1"]
                              .getService(Components.interfaces.nsIIOService);
    
    if (searchableCollection(allYourStuff).contains(theStuffYouWant) &&
        !ambientNotification.isActive() && (client.isAmbientSupported() ||
                                            client.alwaysTryAmbientAnyways())) {
      ambientNotification.activate();
    }
  • Binary and Ternary Operators

    var x = a ? b : c;  // All on one line if it will fit.
    
    // Indentation +4 is OK.
    var y = a ?
        longButSimpleOperandB : longButSimpleOperandC;
    
    // Indenting to the line position of the first operand is also OK.
    var z = a ?
            moreComplicatedB :
            moreComplicatedC;

String

  • Prefer ' over " For consistency single-quotes (') are preferred to double-quotes ("). This is helpful when creating strings that include HTML:

    var msg = 'This is some HTML';

JavaScript tidbits

  • True and False Boolean Expressions The following are all false in boolean expressions:

    • null
    • undefined
    • '' the empty string
    • 0 the number

      But be careful, because these are all true:

    • '0' the string

    • [] the empty array
    • {} the empty object

    Instead of this:

    while (x != null) {

    you can write this:

    while (x) {

    Instead of this:

    if (y != null && y != '') {

    you can write this:

    if (y) {
  • There are some unintuitive things:

    Boolean('0') == true
    '0' != true
    
    0 != null
    0 == []
    0 == false
    
    Boolean(null) == false
    null != true
    null != false
    
    Boolean(undefined) == false
    undefined != true
    undefined != false
    
    Boolean([]) == true
    [] != true
    [] == false
    
    Boolean({}) == true
    {} != true
    {} != false
  • Ternary Operator

    Instead of this:

    if (val != 0) {
      return foo();
    } else {
      return bar();
    }

    you can write this:

    return val ? foo() : bar();

    The ternary conditional is also useful when generating HTML:

    var html = '<input type="checkbox"' +
        (isChecked ? ' checked' : '') +
        (isEnabled ? '' : ' disabled') +
        ' name="foo">';
  • && and ||

    • ||, the default operator. Instead of this:

      /** @param {*=} opt_win */
      function foo(opt_win) {
        var win;
        if (opt_win) {
          win = opt_win;
        } else {
          win = window;
        }
        // ...
      }

      you can write this:

      /** @param {*=} opt_win */
      function foo(opt_win) {
        var win = opt_win || window;
        // ...
      }
    • "&&" is also useful for shortening code. Instead of this:

      if (node) {
        if (node.kids) {
          if (node.kids[index]) {
            foo(node.kids[index]);
          }
        }
      }

      you could do this:

      if (node && node.kids && node.kids[index]) {
        foo(node.kids[index]);
      }
  • Use join() to Build Strings It's common to see this:

    function listHtml(items) {
      var html = '<div class="foo">';
      for (var i = 0; i < items.length; ++i) {
        if (i > 0) {
          html += ', ';
        }
        html += itemHtml(items[i]);
      }
      html += '</div>';
      return html;
    }

    but this is slow in IE, you could better do this:

    function listHtml(items) {
      var html = [];
      for (var i = 0; i < items.length; ++i) {
        html[i] = itemHtml(items[i]);
      }
      return '<div class="foo">' + html.join(', ') + '</div>';
    }

    Assigning values to an array is faster than using push().

JavaScript Object Oriented Programming

JavaScript doesn't support the concept of classes in nature. We need some javascript patterns to emulate this behaviour. Thanks to Douglas Crockford.

Constructor and Public Members

  • In JavaScript, constructors are just function, constructor function. We use this to create objects from this function. Usually, we capitalize the name of constructor function and use the 'new' operator to instantiate a new object.
  • Inside a constructor, the keyword 'this' references the new object that's being created. We define properties inside constructor and define methods inside the constructor's prototype.
  • With this pattern, all methods are created off of the prototype, which means there is only one copy of each in memory, no matter how many instances you create.

    function Car(model, year, miles) {
    
      // public attributes
      this.model = model;
      this.year = year;
      this.miles = miles;
    }
    
    /*
     Note here that we are using Object.prototype.newMethod rather than 
     Object.prototype so as to avoid redefining the prototype object.
     Public methods
    */
    Car.prototype.toString = function () {
      return this.model + " has done " + this.miles + " miles";
    };
    
    var civic = new Car("Honda Civic", 2009, 20000);
    var mondeo = new Car("Ford Mondeo", 2010, 5000);
    
    console.log(civic.toString());

Private Members

  • There are two usual ways to implement private members:

    • through naming convention: an underscore is added to name of members. This signifies they are for internal use only.

      function Car(model, year, miles) {
      
        // private members through convention
        this._model = model;
        this._year = year;
        this._miles = miles;
      }
      
      Car.prototype._setAttribute(name, value) {
          this["_" + name] = value;
      };
      
      Car.prototype.setModel = function(model) {
          this._setAttribute('model', model);
      };
      
      Car.prototype.getModel = function () {
        return this._model;
      };
    • through closure: the downside is that all members are not in the constructor's prototype object. Therefore, each methods are not shared between instances.

      function Car(model, year, miles) {
      
        // private attributes and methods
        var _model = model;
        var _year = year;
        var _miles = miles;
        var _setModel = function(value) {
          // do some checking
          _model = value;
        };
      
        // public methods
        this.getModel = function () {
          return _model;
        };
      
        this.setModel = function(model) {
          _setModel(model);
        };
      }

Static Members

```JavaScript
var Book = (function () {
  var numOfBooks = 0; // Private static attributes. 
  function checkIsbn(isbn) { // Private static method. ... } 

  // Return the constructor. 
  return function(newIsbn, newTitle, newAuthor) { 
    var isbn; // Private attributes.
    this.getIsbn = function () { // Privileged methods. 
      return isbn;
    };

    this.setIsbn = function(newIsbn) {
      if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.');
      isbn = newIsbn;
    }; 
    numOfBooks++; // Keep track of how many Books have been created 
    this.setIsbn(newIsbn);
  }
})();

Book.convertToTitleCase = function(inputString) { // Public static method.
  ...
};
Book.prototype = { // Public, non-privileged methods.
  display: function () {
    ...
  } 
}; 
```

Inheritance

```JavaScript
function Employee(name, dept) {
  this.name = name || "";
  this.dept = dept || "";
}

function Engineer(name, machine) {

  // 1. calling the superclass's constructor
  Employee.call(this, name, "engineering");
  this.machine = machine;
}

// 2. setup the prototype chain
Engineer.prototype = new Employee();
```

Mixin

JavaScript Common Patterns

The Module Pattern

  • This is the most common pattern in JavaScript. It was originally formally defined by Douglas Crockford (JavaScript: The Good Parts).
  • You probably have heard about "Global variables are evil", and this pattern allows us to define public/private members which means that you avoid polluting or clobbering the global namespaces. You will have a level of shielding from external entities accessing your 'hidden' information.

    var someModule = (function () {
    
      //private attributes
      var privateVar = 5;
    
      //private methods
      var privateMethod = function () {
        return 'Private Test';
      };
    
      return {
    
        //public attributes
        publicVar: 10,
    
        //public methods
        publicMethod: function () {
          return ' Followed By Public Test ';
        },
    
        //let's access the private members
        getData: function () {
          return privateMethod() + this.publicMethod() + privateVar;
        }
      }
    })(); //the parent here cause the anonymous function to execute and return
    
    someModule.getData();

The Revealing Module Pattern

  • Here is a slightly improved version - Christian Heilmann’s Revealing Module pattern
  • The benefits of this pattern is that it makes very clear at the end which of your functions and variables may be accessed publicly. Moreover, you are able to reveal private functions with different name.

    var myRevealingModule = (function () {
      var name = 'John Smith';
      var age = 40;
    
      function updatePerson() {
        name = 'John Smith Updated';
      }
      function setPerson() {
         name = 'John Smith Set';
      }
      function getPerson() {
         return name;
      }
    
      return {
          set: setPerson,
          get: getPerson
      };
    })();
    
    // Sample usage:
    myRevealingModule.get();

References