Skip to content

toptive/js-style-guide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

84 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JavaScript Style Guide

Node.js Style Guide

This is a guide for writing consistent and aesthetically pleasing node.js code. It is inspired by what is popular within the community, and flavored with some personal opinions.

There is a .jshintrc which enforces these rules as closely as possible. You can either use that and adjust it, or use this script to make your own.

This guide was created by Felix Geisendörfer and is licensed under the CC BY-SA 3.0 license. You are encouraged to fork this repository and make adjustments according to your preferences.

Creative Commons License

Table of contents

Formatting

Naming Conventions

Variables

Conditionals

Functions

Comments

Miscellaneous

Formatting

2 Spaces for indentation

Use 2 spaces for indenting your code and swear an oath to never mix tabs and spaces - a special kind of hell is awaiting you otherwise.

Newlines

Use UNIX-style newlines (\n), and a newline character as the last character of a file. Windows-style newlines (\r\n) are forbidden inside any repository.

No trailing whitespace

Just like you brush your teeth after every meal, you clean up any trailing whitespace in your JS files before committing. Otherwise the rotten smell of careless neglect will eventually drive away contributors and/or co-workers.

Use Semicolons

According to scientific research, the usage of semicolons is a core value of our community. Consider the points of the opposition, but be a traditionalist when it comes to abusing error correction mechanisms for cheap syntactic pleasures.

80 characters per line

Limit your lines to 80 characters. Yes, screens have gotten much bigger over the last few years, but your brain has not. Use the additional room for split screen, your editor supports that, right?

Use single quotes

Use single quotes, unless you are writing JSON.

Right:

var foo = 'bar';

Wrong:

var foo = "bar";

Opening braces go on the same line

Your opening braces go on the same line as the statement. Also, notice the use of whitespace before and after the condition statement.

Right:

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

Wrong:

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

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

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

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

PS: If you are using sublime-text, give it a try to the sublime conditions snippets.

Declare one variable per var statement

Declare one variable per var statement, it makes it easier to re-order the lines. However, ignore Crockford when it comes to declaring variables deeper inside a function, just put the declarations wherever they make sense.

Right:

var keys   = ['foo', 'bar'];
var values = [23, 42];

var object = {};
while (keys.length) {
  var key = keys.pop();
  object[key] = values.pop();
}

Wrong:

var keys = ['foo', 'bar'],
    values = [23, 42],
    object = {},
    key;

while (keys.length) {
  key = keys.pop();
  object[key] = values.pop();
}

Naming Conventions

Use lowerCamelCase for variables, properties and function names

Variables, properties and function names should use lowerCamelCase. They should also be descriptive. Single character variables and uncommon abbreviations should generally be avoided.

Right:

var adminUser = db.query('SELECT * FROM users ...');

Wrong:

var admin_user = db.query('SELECT * FROM users ...');

Use UpperCamelCase for class names

Class names should be capitalized using UpperCamelCase.

Right:

function BankAccount() {
}

Wrong:

function bank_Account() {
}

PS: If you are using sublime-text, give it a try to the sublime functions snippets.

Use UPPERCASE for Constants

Constants should be declared as regular variables or static class properties, using all uppercase letters.

Right:

var SECOND = 1 * 1000;

function File() {
}
File.FULL_PERMISSIONS = 0777;

Wrong:

const SECOND = 1 * 1000;

function File() {
}
File.fullPermissions = 0777;

Variables

Object / Array creation

Use trailing commas and put short declarations on a single line. Only quote keys when your interpreter complains:

Right:

var a = ['hello', 'world'];
var b = {
  good: 'code',
  'is generally': 'pretty',
};

Wrong:

var a = [
  'hello', 'world'
];
var b = {"good": 'code'
        , is generally: 'pretty'
        };

Conditionals

Use the === operator

Programming is not about remembering stupid rules. Use the triple equality operator as it will work just as expected.

Right:

var a = 0;
if (a !== '') {
  console.log('winning');
}

Wrong:

var a = 0;
if (a == '') {
  console.log('losing');
}

Use descriptive conditions

Any non-trivial conditions should be assigned to a descriptively named variable or function:

Right:

var isValidPassword = password.length >= 4 && /^(?=.*\d).{4,}$/.test(password);

if (isValidPassword) {
  console.log('winning');
}

Wrong:

if (password.length >= 4 && /^(?=.*\d).{4,}$/.test(password)) {
  console.log('losing');
}

Functions

Write small functions

Keep your functions short. A good function fits on a slide that the people in the last row of a big room can comfortably read. So don't count on them having perfect vision and limit yourself to ~15 lines of code per function.

Return early from functions

To avoid deep nesting of if-statements, always return a function's value as early as possible.

Right:

function isPercentage(val) {
  if (val < 0) {
    return false;
  }

  if (val > 100) {
    return false;
  }

  return true;
}

Wrong:

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

Or for this particular example it may also be fine to shorten things even further:

function isPercentage(val) {
  var isInRange = (val >= 0 && val <= 100);
  return isInRange;
}

Name your closures

Feel free to give your closures a name. It shows that you care about them, and will produce better stack traces, heap and cpu profiles.

Right:

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

Wrong:

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

No nested closures

Use closures, but don't nest them. Otherwise your code will become a mess.

Right:

setTimeout(function() {
  client.connect(afterConnect);
}, 1000);

function afterConnect() {
  console.log('winning');
}

Wrong:

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

Method chaining

One method per line should be used if you want to chain methods.

You should also indent these methods so it's easier to tell they are part of the same chain.

Right:

User
  .findOne({ name: 'foo' })
  .populate('bar')
  .exec(function(err, user) {
    return true;
  });

Wrong:

User
.findOne({ name: 'foo' })
.populate('bar')
.exec(function(err, user) {
  return true;
});

User.findOne({ name: 'foo' })
  .populate('bar')
  .exec(function(err, user) {
    return true;
  });

User.findOne({ name: 'foo' }).populate('bar')
.exec(function(err, user) {
  return true;
});

User.findOne({ name: 'foo' }).populate('bar')
  .exec(function(err, user) {
    return true;
  });

Comments

Use slashes for simple comments

Use slashes for both single line and multi line comments. Try to write comments that explain higher level mechanisms or clarify difficult segments of your code. Don't use comments to restate trivial things.

Right:

// 'ID_SOMETHING=VALUE' -> ['ID_SOMETHING=VALUE', 'SOMETHING', 'VALUE']
var matches = item.match(/ID_([^\n]+)=([^\n]+)/));

Use double asterisk for function comments

/** 
* function description
* 
* @param paramType id paramDescription
* @param paramType cb paramDescription
* @return returnType returnDescription
*/
function loadUser(id, cb) {
  //...
}

var isSessionValid = (session.expires < Date.now());
if (isSessionValid) {
  // ...
}

Wrong:

// Execute a regex
var matches = item.match(/ID_([^\n]+)=([^\n]+)/);

// Usage: loadUser(5, function() { ... })
function loadUser(id, cb) {
  // ...
}

// Check if the session is valid
var isSessionValid = (session.expires < Date.now());
// If the session is valid
if (isSessionValid) {
  // ...
}

Miscellaneous

Object.freeze, Object.preventExtensions, Object.seal, with, eval

Crazy shit that you will probably never need. Stay away from it.

Requires At Top

Always put requires at top of file to clearly illustrate a file's dependencies. Besides giving an overview for others at a quick glance of dependencies and possible memory impact, it allows one to determine if they need a package.json file should they choose to use the file elsewhere.

Getters and setters

Do not use setters, they cause more problems for people who try to use your software than they can solve.

Feel free to use getters that are free from side effects, like providing a length property for a collection class.

Do not extend built-in prototypes

Do not extend the prototype of native JavaScript objects. Your future self will be forever grateful.

Right:

var a = [];
if (!a.length) {
  console.log('winning');
}

Wrong:

Array.prototype.empty = function() {
  return !this.length;
}

var a = [];
if (a.empty()) {
  console.log('losing');
}

Angular styleguide

Opinionated Angular styleguide for teams by @toddmotto

A standardised approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, conventions and is based on my experience writing about, talking about, and building Angular applications.

Community

John Papa and I have discussed in-depth styling patterns for Angular and as such have both released separate styleguides. Thanks to those discussions, I've learned some great tips from John that have helped shape this guide. We've both created our own take on a styleguide. I urge you to check his out to compare thoughts.

See the original article that sparked this off

Table of Contents

  1. Modules
  2. Controllers
  3. Services and Factory
  4. Directives
  5. Filters
  6. Routing resolves
  7. Publish and subscribe events
  8. Performance
  9. Angular wrapper references
  10. Comment standards
  11. Minification and annotation
  12. File Templates and Snippets

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax

    // avoid
    var app = angular.module('app', []);
    app.controller();
    app.factory();
    
    // recommended
    angular
      .module('app', [])
      .controller()
      .factory();
  • Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once and get for all other instances.

  • Methods: Pass functions into module methods rather than assign as a callback

    // avoid
    angular
      .module('app', [])
      .controller('MainCtrl', function MainCtrl () {
    
      })
      .service('SomeService', function SomeService () {
    
      });
    
    // recommended
    function MainCtrl () {
    
    }
    function SomeService () {
    
    }
    angular
      .module('app', [])
      .controller('MainCtrl', MainCtrl)
      .service('SomeService', SomeService);
  • ES6 Classes are not hoisted, which will break your code if you rely on hoisting

  • This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

  • IIFE scoping: To avoid polluting the global scope with our function declarations that get passed into Angular, ensure build tasks wrap the concatenated files inside an IIFE

    (function () {
    
      angular
        .module('app', []);
      
      // MainCtrl.js
      function MainCtrl () {
    
      }
      
      angular
        .module('app')
        .controller('MainCtrl', MainCtrl);
      
      // SomeService.js
      function SomeService () {
    
      }
      
      angular
        .module('app')
        .service('SomeService', SomeService);
        
      // ...
        
    })();

Back to top

Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid -->
    <div ng-controller="MainCtrl">
      {{ someObject }}
    </div>
    
    <!-- recommended -->
    <div ng-controller="MainCtrl as vm">
      {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets bound to $scope

    // avoid
    function MainCtrl ($scope) {
      $scope.someObject = {};
      $scope.doSomething = function () {
    
      };
    }
    
    // recommended
    function MainCtrl () {
      this.someObject = {};
      this.doSomething = function () {
    
      };
    }
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on or $watch. Try to limit the use of these, however, and treat $scope as a special use case

  • Inheritance: Use prototypal inheritance when extending controller classes

    function BaseCtrl () {
      this.doSomething = function () {
    
      };
    }
    BaseCtrl.prototype.someObject = {};
    BaseCtrl.prototype.sharedSomething = function () {
    
    };
    
    AnotherCtrl.prototype = Object.create(BaseCtrl.prototype);
    
    function AnotherCtrl () {
      this.anotherSomething = function () {
    
      };
    }
  • Use Object.create with a polyfill for browser support

  • controllerAs 'vm': Capture the this context of the Controller using vm, standing for ViewModel

    // avoid
    function MainCtrl () {
      var doSomething = function () {
    
      };
      this.doSomething = doSomething;
    }
    
    // recommended
    function MainCtrl () {
      var vm = this;
      var doSomething = function () {
        
      };
      vm.doSomething = doSomething;
    }

    Why? : Function context changes the this value, use it to avoid .bind() calls and scoping issues

  • ES6: Avoid var vm = this; when using ES6

    // avoid
    function MainCtrl () {
      let vm = this;
      let doSomething = arg => {
        console.log(vm);
      };
      
      // exports
      vm.doSomething = doSomething;
    }
    
    // recommended
    function MainCtrl () {
      
      let doSomething = arg => {
        console.log(this);
      };
      
      // exports
      this.doSomething = doSomething;
      
    }

    Why? : Use ES6 arrow functions when necessary to access the this value lexically

  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    // avoid
    function MainCtrl () {
      
      var vm = this;
    
      $http
        .get('/users')
        .success(function (response) {
          vm.users = response;
        });
    
      vm.removeUser = function (user, index) {
        $http
          .delete('/user/' + user.id)
          .then(function (response) {
            vm.users.splice(index, 1);
          });
      };
    
    }
    
    // recommended
    function MainCtrl (UserService) {
    
      var vm = this;
    
      UserService
        .getUsers()
        .then(function (response) {
          vm.users = response;
        });
    
      vm.removeUser = function (user, index) {
        UserService
          .removeUser(user)
          .then(function (response) {
            vm.users.splice(index, 1);
          });
      };
    
    }

    Why? : Controllers should fetch Model data from Services, avoiding any Business logic. Controllers should act as a ViewModel and control the data flowing between the Model and the View presentational layer. Business logic in Controllers makes testing Services impossible.

Back to top

Services and Factory

  • All Angular Services are singletons, using .service() or .factory() differs the way Objects are created.

Services: act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

```javascript
function SomeService () {
  this.someMethod = function () {

  };
}
angular
  .module('app')
  .service('SomeService', SomeService);
```

Factory: Business logic or provider modules, return an Object or closure

  • Always return a host Object instead of the revealing Module pattern due to the way Object references are bound and updated

    function AnotherService () {
      var AnotherService = {};
      AnotherService.someValue = '';
      AnotherService.someMethod = function () {
    
      };
      return AnotherService;
    }
    angular
      .module('app')
      .factory('AnotherService', AnotherService);

    Why? : Primitive values cannot update alone using the revealing module pattern

Back to top

Directives

  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    <!-- avoid -->
    
    <!-- directive: my-directive -->
    <div class="my-directive"></div>
    
    <!-- recommended -->
    
    <my-directive></my-directive>
    <div my-directive></div>
  • Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is the safest method for browser coverage.

  • Templating: Use Array.join('') for clean templating

    // avoid
    function someDirective () {
      return {
        template: '<div class="some-directive">' +
          '<h1>My directive</h1>' +
        '</div>'
      };
    }
    
    // recommended
    function someDirective () {
      return {
        template: [
          '<div class="some-directive">',
            '<h1>My directive</h1>',
          '</div>'
        ].join('')
      };
    }

    Why? : Improves readability as code can be indented properly, it also avoids the + operator which is less clean and can lead to errors if used incorrectly to split lines

  • DOM manipulation: Takes place only inside Directives, never a controller/service

    // avoid
    function UploadCtrl () {
      $('.dragzone').on('dragend', function () {
        // handle drop functionality
      });
    }
    angular
      .module('app')
      .controller('UploadCtrl', UploadCtrl);
    
    // recommended
    function dragUpload () {
      return {
        restrict: 'EA',
        link: function (scope, element, attrs) {
          element.on('dragend', function () {
            // handle drop functionality
          });
        }
      };
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);
  • Naming conventions: Never ng-* prefix custom directives, they might conflict future native directives

    // avoid
    // <div ng-upload></div>
    function ngUpload () {
      return {};
    }
    angular
      .module('app')
      .directive('ngUpload', ngUpload);
    
    // recommended
    // <div drag-upload></div>
    function dragUpload () {
      return {};
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);
  • Directives and Filters are the only providers that have the first letter as lowercase; this is due to strict naming conventions in Directives. Angular hyphenates camelCase, so dragUpload will become <div drag-upload></div> when used on an element.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    // avoid
    function dragUpload () {
      return {
        controller: function ($scope) {
    
        }
      };
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);
    
    // recommended
    function dragUpload () {
      return {
        controllerAs: 'vm',
        controller: function () {
    
        }
      };
    }
    angular
      .module('app')
      .directive('dragUpload', dragUpload);

Back to top

Filters

  • Global filters: Create global filters using angular.filter() only. Never use local filters inside Controllers/Services

    // avoid
    function SomeCtrl () {
      this.startsWithLetterA = function (items) {
        return items.filter(function (item) {
          return /^a/i.test(item.name);
        });
      };
    }
    angular
      .module('app')
      .controller('SomeCtrl', SomeCtrl);
    
    // recommended
    function startsWithLetterA () {
      return function (items) {
        return items.filter(function (item) {
          return /^a/i.test(item.name);
        });
      };
    }
    angular
      .module('app')
      .filter('startsWithLetterA', startsWithLetterA);
  • This enhances testing and reusability

Back to top

Routing resolves

  • Promises: Resolve Controller dependencies in the $routeProvider (or $stateProvider for ui-router), not the Controller itself

    // avoid
    function MainCtrl (SomeService) {
      var _this = this;
      // unresolved
      _this.something;
      // resolved asynchronously
      SomeService.doSomething().then(function (response) {
        _this.something = response;
      });
    }
    angular
      .module('app')
      .controller('MainCtrl', MainCtrl);
    
    // recommended
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        resolve: {
          // resolve here
        }
      });
    }
    angular
      .module('app')
      .config(config);
  • Controller.resolve property: Never bind logic to the router itself. Reference a resolve property for each Controller to couple the logic

    // avoid
    function MainCtrl (SomeService) {
      this.something = SomeService.something;
    }
    
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controllerAs: 'vm',
        controller: 'MainCtrl'
        resolve: {
          doSomething: function () {
            return SomeService.doSomething();
          }
        }
      });
    }
    
    // recommended
    function MainCtrl (SomeService) {
      this.something = SomeService.something;
    }
    
    MainCtrl.resolve = {
      doSomething: function (SomeService) {
        return SomeService.doSomething();
      }
    };
    
    function config ($routeProvider) {
      $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controllerAs: 'vm',
        controller: 'MainCtrl'
        resolve: MainCtrl.resolve
      });
    }
  • This keeps resolve dependencies inside the same file as the Controller and the router free from logic

Back to top

Publish and subscribe events

  • $scope: Use the $emit and $broadcast methods to trigger events to direct relationship scopes only

    // up the $scope
    $scope.$emit('customEvent', data);
    
    // down the $scope
    $scope.$broadcast('customEvent', data);
  • $rootScope: Use only $emit as an application-wide event bus and remember to unbind listeners

    // all $rootScope.$on listeners
    $rootScope.$emit('customEvent', data);
  • Hint: Because the $rootScope is never destroyed, $rootScope.$on listeners aren't either, unlike $scope.$on listeners and will always persist, so they need destroying when the relevant $scope fires the $destroy event

    // call the closure
    var unbind = $rootScope.$on('customEvent'[, callback]);
    $scope.$on('$destroy', unbind);
  • For multiple $rootScope listeners, use an Object literal and loop each one on the $destroy event to unbind all automatically

    var unbind = [
      $rootScope.$on('customEvent1'[, callback]),
      $rootScope.$on('customEvent2'[, callback]),
      $rootScope.$on('customEvent3'[, callback])
    ];
    $scope.$on('$destroy', function () {
      unbind.forEach(function (fn) {
        fn();
      });
    });

Back to top

Performance

  • One-time binding syntax: In newer versions of Angular (v1.3.0-beta.10+), use the one-time binding syntax {{ ::value }} where it makes sense

    // avoid
    <h1>{{ vm.title }}</h1>
    
    // recommended
    <h1>{{ ::vm.title }}</h1>

    Why? : Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance in each dirty-check

  • Consider $scope.$digest: Use $scope.$digest over $scope.$apply where it makes sense. Only child scopes will update

    $scope.$digest();

    Why? : $scope.$apply will call $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check current and child scopes from the initiated $scope

Back to top

Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

    // avoid
    function dragUpload () {
      return {
        link: function ($scope, $element, $attrs) {
          document.addEventListener('click', function () {
    
          });
        }
      };
    }
    
    // recommended
    function dragUpload ($document) {
      return {
        link: function ($scope, $element, $attrs) {
          $document.addEventListener('click', function () {
    
          });
        }
      };
    }
  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    // avoid
    function dragUpload () {
      return {
        link: function ($scope, $element, $attrs) {
          setTimeout(function () {
            //
          }, 1000);
        }
      };
    }
    
    // recommended
    function dragUpload ($timeout) {
      return {
        link: function ($scope, $element, $attrs) {
          $timeout(function () {
            //
          }, 1000);
        }
      };
    }

Back to top

Comment standards

  • jsDoc: Use jsDoc syntax to document function names, description, params and returns

    /**
     * @name SomeService
     * @desc Main application Controller
     */
    function SomeService (SomeService) {
    
      /**
       * @name doSomething
       * @desc Does something awesome
       * @param {Number} x - First number to do something with
       * @param {Number} y - Second number to do something with
       * @returns {Number}
       */
      this.doSomething = function (x, y) {
        return x * y;
      };
    
    }
    angular
      .module('app')
      .service('SomeService', SomeService);

Back to top

Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /** @ngInject */

    /**
     * @ngInject
     */
    function MainCtrl (SomeService) {
      this.doSomething = SomeService.doSomething;
    }
    angular
      .module('app')
      .controller('MainCtrl', MainCtrl);
  • Which produces the following output with the $inject annotation

    /**
     * @ngInject
     */
    function MainCtrl (SomeService) {
      this.doSomething = SomeService.doSomething;
    }
    MainCtrl.$inject = ['SomeService'];
    angular
      .module('app')
      .controller('MainCtrl', MainCtrl);

Back to top

File Templates and Snippets

Use file templates or snippets to help follow consistent styles and patterns. Here are templates and/or snippets for some of the web development editors and IDEs.

Sublime Text

[Style Y250]
  • Angular snippets that follow these styles and guidelines.

    • Download the Sublime Angular snippets
    • Place it in your Packages folder
    • Restart Sublime
    • In a JavaScript file type these commands followed by a TAB
    ngcontroller // creates an Angular controller
    ngdirective  // creates an Angular directive
    ngfactory    // creates an Angular factory
    ngmodule     // creates an Angular module
    ngservice    // creates an Angular service
    ngfilter     // creates an Angular filter

Back to top

Angular docs

For anything else, including API reference, check the Angular documentation.

About

A styling guide / JavaScript code.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 100.0%