Skip to content

angelyordanov/js-style-guide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

88 Commits
 
 
 
 

Repository files navigation

JavaScript Style Guide

A mostly reasonable approach to JavaScript

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

    • string
    • number
    • boolean
    • null
    • undefined
    var foo = 1,
        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],
        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 = [],
        i;
    
    // bad
    for (i = 0; i < 100; i++) {
        hundredOdds.push(i * 2 + 1);
    }
    
    // good
    for (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,
        itemsCopy = [],
        i;
    
    // bad
    for (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;
  • Longer strings 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,
        messages,
        length, i;
    
    messages = [{
        state: 'success',
        message: 'This one worked.'
    },{
        state: 'success',
        message: 'This one worked as well.'
    },{
        state: 'error',
        message: 'This one did not work.'
    }];
    
    length = messages.length;
    
    // bad
    function inbox(messages) {
        items = '<ul>';
    
        for (i = 0; i < length; i++) {
            items += '<li>' + messages[i].message + '</li>';
        }
    
        return items + '</ul>';
    }
    
    // good
    function inbox(messages) {
        items = [];
    
        for (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.');
    })();
  • 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.');
        };
    }
  • 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...
    }

    [⬆]

  • 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 for multiple variables and declare each variable on a newline.

    // bad
    var items = getItems();
    var goSportsTeam = true;
    var dragonball = 'z';
    
    // good
    var items = getItems(),
        goSportsTeam = true,
        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.

    // bad
    var i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;
    
    // bad
    var i, items = getItems(),
        dragonball,
        goSportsTeam = true,
        len;
    
    // good
    var items = getItems(),
        goSportsTeam = true,
        dragonball,
        i, length;
  • Assign variables at the top of their scope. This helps avoid issues with variable declaration and assignment hoisting related issues.

    // 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;
    }

    [⬆]

  • Variable declarations get hoisted to the top of their scope, their assignment does not.

    // we know this wouldn't work (assuming there
    // is no notDefined global variable)
    function example() {
        console.log(notDefined); // => throws a ReferenceError
    }
    
    // creating a variable declaration after you
    // reference the variable will work due to
    // variable hoisting. Note: the assignment
    // value of `true` is not hoisted.
    function example() {
        console.log(declaredButNotAssigned); // => undefined
        var declaredButNotAssigned = true;
    }
    
    // The interpretor is hoisting the variable
    // declaration to the top of the scope.
    // Which means our example could be rewritten as:
    function example() {
        var declaredButNotAssigned;
        console.log(declaredButNotAssigned); // => undefined
        declaredButNotAssigned = true;
    }
  • Anonymous function expression hoist their variable name, but not the function assignment.

    function example() {
        console.log(anonymous); // => undefined
    
        anonymous(); // => TypeError anonymous is not a function
    
        var anonymous = function() {
            console.log('anonymous function expression');
        };
    }
  • Named function expressions hoist the variable name, not the function name or the function body.

    function example() {
        console.log(named); // => undefined
    
        named(); // => TypeError named is not a function
    
        superPower(); // => ReferenceError superPower is not defined
    
        var named = function superPower() {
            console.log('Flying');
        };
    
    
        // the same is true when the function name
        // is the same as the variable name.
        function example() {
            console.log(named); // => undefined
    
            named(); // => TypeError named is not a function
    
            var named = function named() {
                console.log('named');
            };
        }
    }
  • Function declarations hoist their name and the function body.

    function example() {
        superPower(); // => Flying
    
        function superPower() {
            console.log('Flying');
        }
    }
  • For more information refer to JavaScript Scoping & Hoisting by Ben Cherry

    [⬆]

  • Use === and !== over == and !=.

  • 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
    if (name) {
        // ...stuff...
    }
    
    // bad
    if (collection.length > 0) {
        // ...stuff...
    }
    
    // good
    if (collection.length) {
        // ...stuff...
    }
  • For more information see Truth Equality and JavaScript by Angus Croll

    [⬆]

  • Use braces with all multi-line blocks.

    // bad
    if (test)
        return false;
    
    // good
    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.

    // 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 set to 4 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();
  • Use indentation when making long method calls. Indent once for every nested method call.

    // bad
    self._actionQueue.unshift(self._createExecution(actionConfig.dependencies[i], params, cancelationToken, false));
    
    // good
    self._actionQueue
        .unshift(
            self._createExecution(
                actionConfig.dependencies[i],
                params,
                cancelationToken,
                false));

    [⬆]

  • Nope.

    // bad
    var once
      , upon
      , aTime;
    
    // good
    var once,
        upon,
        aTime;
    
    // bad
    var hero = {
        firstName: 'Bob'
      , lastName: 'Parr'
      , heroName: 'Mr. Incredible'
      , superPower: 'strength'
    };
    
    // good
    var hero = {
        firstName: 'Bob',
        lastName: 'Parr',
        heroName: 'Mr. Incredible',
        superPower: 'strength'
    };

    [⬆]

  • Yup.

    // bad
    (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 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);
    };

    [⬆]

  • All modules should follow the AMD pattern.

  • Each module should export a constructor function or an instance of an object for 'singleton' modules.

  • Every module should be in its own file with no other modules inside. The 'main' module is an exception.

  • The file should be named with underscore separated lowercased letters that match the name of the constructor function.

  • Always declare 'use strict'; at the top module function.

    // view_models/user_vm.js
    
    define([
        //libs
        'jquery'
    ], function ($, ko, Class) {
        'use strict';
    
        var MyModuleConstructor = function () {};
        return MyModuleConstructor;
    });

    [⬆]

  • As javascript does not provide a class system we create a convention based class system taken from BackboneJS 'Class.extend'.

  • Always use 'Class.extend' to create constructor functions for classes.

    // bad
    function Jedi() {
        console.log('new jedi');
    }
    
    Jedi.prototype.fight = function fight() {
        console.log('fighting');
    };
    
    Jedi.prototype.block = function block() {
        console.log('blocking');
    };
    
    // good
    var Jedi = Class.extend({
        constructor: function () {
            console.log('new jedi');
        },
        fight: function fight() {
            console.log('fighting');
        },
        block: function block() {
            console.log('blocking');
        }
    });
    • Always use a saved reference to 'this' named 'self' when accessing it inside 'Class' methods.
    // bad
    var Jedi = Class.extend({
        constructor: function () {
            this._height = height;
        }
    });
    
    // good
    var Jedi = Class.extend({
        constructor: function () {
            var self = this;
    
            self._height = height;
        }
    });
  • Always initialize all public/private properties in the constructor for easier maintainance and performace reasons Initialize all object members in constructor functions.

    // bad
    var Jedi = Class.extend({
        constructor: function () {
        },
        height: function(height) {
            var self = this;
    
            self._height = height;
        }
    });
    
    // good
    var Jedi = Class.extend({
        constructor: function () {
            var self = this;
    
            self._height = undefined;
        },
        height: function(height) {
            var self = this;
    
            self._height = height;
        }
    });

    [⬆]

  • Accessor functions for private properties are not required.

  • Always create accessor functions for public properties.

  • If you do make accessor functions use a single function - val() and val('newval') instead of getVal() and setVal('hello').

    // bad
    dragon.getAge();
    
    // good
    dragon.age();
    
    // bad
    dragon.setAge(25);
    
    // good
    dragon.age(25);
  • Use 'ko.observable()' and 'ko.observableArray()' to create accessor functions for properties. Its easier that way.

    var Dragon = Class.extend({
        constructor: function () {
            var self = this;
    
            self.age = ko.observable();
        }
    });
  • 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.

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

    [⬆]

  • 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');

    [⬆]

[⬆]

[⬆]

Read This

Other Styleguides

Other Styles

Books

Blogs

[⬆]

This is a fork of the Airbnb JavaScript Style Guide(airbnb/javascript) made to suite my style preferences.

(MIT License)

[⬆]

About

JavaScript Style Guide

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published