Skip to content

Instantly share code, notes, and snippets.

@nuno
Created January 12, 2014 00:40
Show Gist options
  • Save nuno/8378948 to your computer and use it in GitHub Desktop.
Save nuno/8378948 to your computer and use it in GitHub Desktop.
JS Style Guide

JavaScript Style Guide() {

A mostly reasonable approach to JavaScript

  1. Types
  2. Objects
  3. Arrays
  4. Strings
  5. Functions
  6. Properties
  7. Variables
  8. Operators
  9. Hoisting
  10. Conditional Expressions & Equality
  11. Blocks
  12. Comments
  13. Whitespace
  14. Leading Commas
  15. Semicolons
  16. Type Casting & Coercion
  17. Naming Conventions
  18. Accessors
  19. Constructors
  20. Modules
  21. jQuery
  22. ES5 Compatability
  23. Testing
  24. Performance
  25. Resources
  • Primitives: When you access a primitive type you work directly on its value

    • string
    • number
    • boolean
    • null
    • undefined
    var foo = 1;
    var bar = foo;
    
    bar = 9;
    
    console.log(foo, bar); // => 1, 9
  • Complex: When you access a complex type you work on a reference to its value

    • object
    • array
    • function
    var foo = [1, 2];
    var bar = foo;
    
    bar[0] = 9;
    
    console.log(foo[0], bar[0]); // => 9, 9

    [⬆]

  • Use the literal syntax for object creation.

    // bad
    var item = new Object();
    
    // good
    var item = {};
  • Don't use reserved words as keys.

    // bad
    var superman = {
      class: 'superhero',
      default: { clark: kent },
      private: true
    };
    
    // good
    var superman = {
      klass: 'superhero',
      defaults: { clark: kent },
      hidden: true
    };

    [⬆]

  • Use the literal syntax for array creation

    // bad
    var items = new Array();
    
    // good
    var items = [];
  • When you are managing array length use direct assignment over Array#push. jsPerf

    var hundredOdds = [];
    
    // bad
    for (var i = 0; i < 100; i++) {
      hundredOdds.push(i * 2 + 1);
    }
    
    // bad (~6x slower)
    theArray.slice(0);
    
    // good
    for (var i = 0; i < 100; i++) {
      hundredOdds[i] = i * 2 + 1;
    }
  • If you don't know array length use Array#push.

    var someStack = [];
    
    
    // bad
    someStack[someStack.length] = 'abracadabra';
    
    // good
    someStack.push('abracadabra');
  • When you need to copy an array use Array() constructor. jsPerf

    var len = items.length;
    var itemsCopy = [];
    
    // bad
    for (var i = 0; i < len; i++) {
      itemsCopy[i] = items[i];
    }
    
    // good
    itemsCopy = Array.apply(null, items);

    [⬆]

  • Use single quotes '' for strings

    // bad
    var name = "Bob Parr";
    
    // good
    var name = 'Bob Parr';
    
    // bad
    var fullName = "Bob" + this.lastName;
    
    // good
    var fullName = 'Bob' + this.lastName;
  • Strings longer than 80 characters should be written across multiple lines using string concatenation.

    // bad
    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.';
    
    // bad
    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.';
    
    
    // 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.';
  • When programatically building up a string, use Array#join instead of string concatenation. Mostly for IE: jsPerf.

    var items;
    
    var messages = [{
        state: 'success',
        message: 'This one worked.'
    },{
        state: 'success',
        message: 'This one worked as well.'
    },{
        state: 'error',
        message: 'This one did not work.'
    }];
    
    var length = messages.length;
    
    // bad
    function inbox(messages) {
      items = '<ul>';
    
      for (var i = 0; i < length; i++) {
        items += '<li>' + messages[i].message + '</li>';
      }
    
      return items + '</ul>';
    }
    
    // good
    function inbox(messages) {
      items = [];
    
      for (var i = 0; i < length; i++) {
        items[i] = messages[i].message;
      }
    
      return '<ul><li>' + items.join('</li><li>') + '</li></ul>';
    }

    [⬆]

  • Function expressions:

    // anonymous function expression
    var anonymous = function() {
      return true;
    };
    
    // named function expression
    var named = function named() {
      return true;
    };
    
    // immediately-invoked function expression (IIFE)
    (function() {
      console.log('Welcome to the Internet. Please follow me.');
    })();
  • Error is always the first argument in callbacks. This will ensure the user deals with it.

// bad function writeCsvFile(target, data, callback) { convertToCsv(data, function (err, csv) { return callback( csv, target, err); }); }

// good
function writeCsvFile(target, data, callback) {
    convertToCsv(data, function (err, csv) {
        return callback(err, csv, target);
    });
}
```
  • Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead. Browsers will allow you to do it, but they all interpret it differently, which is bad news bears.

    // bad
    if (currentUser) {
      function test() {
        console.log('Nope.');
      }
    }
    
    // good
    if (currentUser) {
      var test = function test() {
        console.log('Yup.');
      };
    }
  • Return on callback

    // bad
    function writeCsvFile(target, data, callback) {
      convertToCsv(data, function (err, csv) {
        if (err) {
          callback(err); // oops! no return
        }
        // this line gets called even when theres an error
        writeFile(target, csv, callback);
       });
     }
    
    // good
    function writeCsvFile(target, data, callback) {
      convertToCsv(data, function (err, csv) {
        if (err) {
          return callback(err); // execution stops here
        }
        writeFile(target, csv, callback);
      });
    }
  • Never name a parameter arguments, this will take precendence over the arguments object that is given to every function scope.

    // bad
    function nope(name, options, arguments) {
      // ...stuff...
    }
    
    // good
    function yup(name, options, args) {
      // ...stuff...
    }
  • No Nested closures

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

    [⬆]

  • Use dot notation when accessing properties.

    var luke = {
      jedi: true,
      age: 28
    };
    
    // bad
    var isJedi = luke['jedi'];
    
    // good
    var isJedi = luke.jedi;
  • Use subscript notation [] when accessing properties with a variable.

    var luke = {
      jedi: true,
      age: 28
    };
    
    function getProp(prop) {
      return luke[prop];
    }
    
    var isJedi = getProp('jedi');

    [⬆]

  • Always use var to declare variables. Not doing so will result in global variables. We want to avoid polluting the global namespace. Captain Planet warned us of that.

    // bad
    superPower = new SuperPower();
    
    // good
    var superPower = new SuperPower();
  • Use one var declaration requiring modules at the top of a file and declare each variable comma first and on a newline. Declare sub-selections of module after all modules have been required.

    // bad
    var io = require('socket.io');
    var mm = require('./my-module').init();
    var myProp = mm.myProperty;
    var express = require('express');
    
    // good
    var io = require('socket.io')
      , mm = require('./my-module').init()
      , express = require('express')
      , myProp = mm.myProperty;
  • For all other variables use var to declare every variable to ovoid accidental global vars or messy syntax.

    // bad
    var items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
    
    // good
    var items = getItems();
    var goSportsTeam = true;
    var dragonball = 'z';
  • Declare unassigned variables last. This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables. Always set the variables to there anticipated type or null if you are unsure.

    // bad
    var i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;
    
    // bad
    var i, items = getItems(),
        dragonball;
    var goSportsTeam = true,
        len;
    
    // good
    var items = getItems();
    var goSportsTeam = true;
    var dragonball = '';
    var i = null;
    var length = 0;
  • Assign variables at the top of their scope. This helps avoid issues with variable declaration and assignment hoisting related issues. If scope spans a large amount of lines try to keep variables close to where they are used.

    // bad
    function() {
      test();
      console.log('doing stuff..');
    
      //..other stuff..
    
      var name = getName();
    
      if (name === 'test') {
        return false;
      }
    
      return name;
    }
    
    // good
    function() {
      var name = getName();
    
      test();
      console.log('doing stuff..');
    
      //..other stuff..
    
      if (name === 'test') {
        return false;
      }
    
      return name;
    }
    
    // bad
    function() {
      var name = getName();
    
      if (!arguments.length) {
        return false;
      }
    
      return true;
    }
    
    // good
    function() {
      if (!arguments.length) {
        return false;
      }
    
      var name = getName();
    
      return true;
    }

    [⬆]

  • Always use the === operator for checking equality.

    // bad
      var a = 0;
     if (a == '') {
      console.log('losing');
    }
    
    // good
    var a = 0;
    if (a === '') {
      console.log('winning');
    }
  • Always use the !== operator for checking inequality.

    // bad
    var a = 0;
    if (a != '') {
      console.log('losing');
    }
    
    // good
    var a = 0;
    if (a !== '') {
      console.log('winning');
    }
  • For more information refer to Why Use Triple Equals JS

    [⬆]

  • Use === and !== over == and != and use !! when testing a single variable or object length to ensure boolean value.

  • Conditional expressions are evaluated using coercion with the ToBoolean method and always follow these simple rules:

    • Objects evaluate to true
    • Undefined evaluates to false
    • Null evaluates to false
    • Booleans evaluate to the value of the boolean
    • Numbers evalute to false if +0, -0, or NaN, otherwise true
    • Strings evaluate to false if an empty string '', otherwise true
    if (!![0]) {
      // true
      // An array is an object, objects evaluate to true
    }
  • Use shortcuts.

    // bad
    if (name !== '') {
      // ...stuff...
    }
    
    // good
    // use `!!` to ensure boolean value
    if (!!name) {
      // ...stuff...
    }
    
    // bad
    if (collection.length > 0) {
      // ...stuff...
    }
    
    // good
    // use `!!` to ensure boolean value
    if (!!collection.length) {
      // ...stuff...
    }
  • For more information see Truth Equality and JavaScript by Angus Croll

    [⬆]

  • Always use braces with blocks.

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

    [⬆]

  • Use /** ... */ for multiline comments. Include a description, specify types and values for all parameters and return values. Always create doc blocks for functions so documentation can be generated.

    // bad
    // make() returns a new element
    // based on the passed in tag name
    //
    // @param <String> tag
    // @return <Element> element
    function make(tag) {
    
      // ...stuff...
    
      return element;
    }
    
    // good
    /**
     * make() returns a new element
     * based on the passed in tag name
     *
     * @param <String> tag
     * @return <Element> element
     */
    function make(tag) {
    
      // ...stuff...
    
      return element;
    }
  • Use // for single line comments. Place single line comments on a newline above the subject of the comment. Put an emptyline before the comment.

    // bad
    var active = true;  // is current tab
    
    // good
    // is current tab
    var active = true;
    
    // 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;
    }

    [⬆]

  • Use soft tabs (aka spaces) set to 2 spaces

    // bad
    function() {
    ∙∙∙∙var name;
    }
    
    // bad
    function() {
    ∙var name;
    }
    
    // good
    function() {
    ∙∙var name;
    }
  • Place 1 space before the leading brace.

    // bad
    function test(){
      console.log('test');
    }
    
    // good
    function test() {
      console.log('test');
    }
    
    // bad
    dog.set('attr',{
      age: '1 year',
      breed: 'Bernese Mountain Dog'
    });
    
    // good
    dog.set('attr', {
      age: '1 year',
      breed: 'Bernese Mountain Dog'
    });
  • Place an empty newline at the end of the file.

    // bad
    (function(global) {
      // ...stuff...
    })(this);
    // good
    (function(global) {
      // ...stuff...
    })(this);

    [⬆]

  • Use indentation when making long method chains.

// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();

// good
$('#items')
  .find('.selected')
    .highlight()
    .end()
  .find('.open')
    .updateCount();

// bad
var leds = stage.selectAll('.led').data(data).enter().append("svg:svg").class('led', true)
    .attr('width',  (radius + margin) * 2).append("svg:g")
    .attr("transform", "translate(" + (radius + margin) + "," + (radius + margin) + ")")
    .call(tron.led);

// good
var leds = stage.selectAll('.led')
    .data(data)
  .enter().append("svg:svg")
    .class('led', true)
    .attr('width',  (radius + margin) * 2)
  .append("svg:g")
    .attr("transform", "translate(" + (radius + margin) + "," + (radius + margin) + ")")
    .call(tron.led);
  • Sometimes to increase readability.

    // bad
    var io = require('socket.io'),
        express = require('express'),
        bodhi = require('./bodhi/dev');
    
    // good
    var io = require('socket.io')
      , express = require('express')
      , bodhi = require('./bodhi/dev');
    
    // bad
    var hero = {
        firstName: 'Bob'
      , lastName: 'Parr'
      , heroName: 'Mr. Incredible'
      , superPower: 'strength'
    };
    
    // good
    var hero = {
      firstName: 'Bob',
      lastName: 'Parr',
      heroName: 'Mr. Incredible',
      superPower: 'strength'
    };

    [⬆]

  • Always.

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

    [⬆]

  • Perform type coercion at the beginning of the statement.

  • Strings:

    //  => this.reviewScore = 9;
    
    // bad
    var totalScore = this.reviewScore + '';
    
    // good
    var totalScore = '' + this.reviewScore;
    
    // bad
    var totalScore = '' + this.reviewScore + ' total score';
    
    // good
    var totalScore = this.reviewScore + ' total score';
  • Use parseInt for Numbers and always with a radix for type casting.

  • If for whatever reason you are doing something wild and parseInt is your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you're doing.

    var inputValue = '4';
    
    // bad
    var val = new Number(inputValue);
    
    // bad
    var val = +inputValue;
    
    // bad
    var val = inputValue >> 0;
    
    // bad
    var val = parseInt(inputValue);
    
    // good
    var val = Number(inputValue);
    
    // good
    var val = parseInt(inputValue, 10);
    
    // good
    /**
     * parseInt was the reason my code was slow.
     * Bitshifting the String to coerce it to a
     * Number made it a lot faster.
     */
    var val = inputValue >> 0;
  • Booleans:

    var age = 0;
    
    // bad
    var hasAge = new Boolean(age);
    
    // good
    var hasAge = Boolean(age);
    
    // good
    var hasAge = !!age;

    [⬆]

  • Avoid single letter names. Be descriptive with your naming.

    // bad
    function q() {
      // ...stuff...
    }
    
    // good
    function query() {
      // ..stuff..
    }
  • Use camelCase when naming objects, functions, and instances

    // bad
    var OBJEcttsssss = {};
    var this_is_my_object = {};
    var this-is-my-object = {};
    function c() {};
    var u = new user({
      name: 'Bob Parr'
    });
    
    // good
    var thisIsMyObject = {};
    function thisIsMyFunction() {};
    var user = new User({
      name: 'Bob Parr'
    });
  • Use PascalCase when naming constructors or classes

    // bad
    function user(options) {
      this.name = options.name;
    }
    
    var bad = new user({
      name: 'nope'
    });
    
    // good
    function User(options) {
      this.name = options.name;
    }
    
    var good = new User({
      name: 'yup'
    });
  • Use a leading underscore _ when naming private properties

    // bad
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    
    // good
    this._firstName = 'Panda';
  • When saving a reference to this use self.

    // bad
    function() {
      var self = this;
      return function() {
        console.log(self);
      };
    }
    
    // bad
    function() {
      var that = this;
      return function() {
        console.log(that);
      };
    }
    
    // good
    function() {
      var self = this;
      return function() {
        console.log(self);
      };
    }
  • Name your functions. This is helpful for stack traces.

    // bad
    var log = function(msg) {
      console.log(msg);
    };
    
    // good
    var log = function log(msg) {
      console.log(msg);
    };

    [⬆]

  • Accessor functions for properties are not required

  • If you do make accessor functions use getVal() and setVal('hello')

    // bad
    dragon.age();
    
    // good
    dragon.getAge();
    
    // bad
    dragon.age(25);
    
    // good
    dragon.setAge(25);
  • If the property is a boolean, use isVal() or hasVal()

    // bad
    if (!dragon.age()) {
      return false;
    }
    
    // good
    if (!dragon.hasAge()) {
      return false;
    }
  • It's okay to create get() and set() functions, but be consistent.

    function Jedi(options) {
      options || (options = {});
      var lightsaber = options.lightsaber || 'blue';
      this.set('lightsaber', lightsaber);
    }
    
    Jedi.prototype.set = function(key, val) {
      this[key] = val;
    };
    
    Jedi.prototype.get = function(key) {
      return this[key];
    };

    [⬆]

  • Assign methods to the prototype object, instead of overwriting the prototype with a new object. Overwriting the prototype makes inheritance impossible: by resetting the prototype you'll overwrite the base!

    function Jedi() {
      console.log('new jedi');
    }
    
    // bad
    Jedi.prototype = {
      fight: function fight() {
        console.log('fighting');
      },
    
      block: function block() {
        console.log('blocking');
      }
    };
    
    // good
    Jedi.prototype.fight = function fight() {
      console.log('fighting');
    };
    
    Jedi.prototype.block = function block() {
      console.log('blocking');
    };
  • Methods can return this to help with method chaining.

    // bad
    Jedi.prototype.jump = function() {
      this.jumping = true;
      return true;
    };
    
    Jedi.prototype.setHeight = function(height) {
      this.height = height;
    };
    
    var luke = new Jedi();
    luke.jump(); // => true
    luke.setHeight(20) // => undefined
    
    // good
    Jedi.prototype.jump = function() {
      this.jumping = true;
      return this;
    };
    
    Jedi.prototype.setHeight = function(height) {
      this.height = height;
      return this;
    };
    
    var luke = new Jedi();
    
    luke.jump()
      .setHeight(20);
  • It's okay to write a custom toString() method, just make sure it works successfully and causes no side effects.

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

    [⬆]

  • The file should be named with camelCase, live in a folder with the same name, and match the name of the single export.

  • Basic Module

    /**
     * Basic module
     */
    
    // module deps
    
    var Emitter = require('events')
      , path = require('path');
    
    // main export
    
    var Util = module.exports = {};
    
    // internal private state
    
    var privateState = 0;
    
    // internal public state
    
    Util.publicState = 0;
    
    /**
     * [ description]
     * @return {[type]} [description]
     */
    
    Util.incModState = function () {
      privateState = privateState + 1;
      Util.publicState = Util.pubilicState + 1;
    };
    
    /**
     * [privFunc description]
     * @return {[type]} [description]
     */
    
    function privFunc(){
      // do something
    }
  • Class Module

    /**
     * Class Module
     */
    
    var Stream = require('stream');
    
    // expose SampleClass
    
    module.exports = SampleClass;
    
    // class constructor
    
    function SampleClass (opts) {
      
      // Allow for instance creation with or without `new`
      
      if(!(this instanceof SampleClass)){ return new SampleClass(opts); }
      
      // create local var for `this` to use in nested functions
      
      var self = this;
    };
    
    // inherit from module
    
    SampleClass.prototype.__proto__ = Stream.prototype;
    
    /**
     * SampleClass Instace Function
     * @return {[type]} [description]
     */
    
    SampleClass.prototype.someFunc = function () {
      var self = this;
    };
    
    /**
     * SampleClass Class Function
     * @return {[type]} [description]
     */
    
    SampleClass.otherFunc = function () {
      var x = 10;
      return x;
    };

    [⬆]

  • Prefix jQuery object variables with a $.

    // bad
    var sidebar = $('.sidebar');
    
    // good
    var $sidebar = $('.sidebar');
  • Cache jQuery lookups.

    // bad
    function setSidebar() {
      $('.sidebar').hide();
    
      // ...stuff...
    
      $('.sidebar').css({
        'background-color': 'pink'
      });
    }
    
    // good
    function setSidebar() {
      var $sidebar = $('.sidebar');
      $sidebar.hide();
    
      // ...stuff...
    
      $sidebar.css({
        'background-color': 'pink'
      });
    }
  • For DOM queries use Cascading $('.sidebar ul') or parent > child $('.sidebar > .ul'). jsPerf

  • Use find with scoped jQuery object queries.

    // bad
    $('.sidebar', 'ul').hide();
    
    // bad
    $('.sidebar').find('ul').hide();
    
    // good
    $('.sidebar ul').hide();
    
    // good
    $('.sidebar > ul').hide();
    
    // good (slower)
    $sidebar.find('ul');
    
    // good (faster)
    $($sidebar[0]).find('ul');

    [⬆]

[⬆]

  • Yup.

    function() {
      return true;
    }

    [⬆]

[⬆]

Read This

Other Styleguides

Other Styles

Books

Blogs

[⬆]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment