Skip to content

kgryte/javascript-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JavaScript Style Guide

An opinionated style guide for writing JavaScript.

Table of Contents

  1. Intro
  2. General Principles
  3. Whitespace
  4. Semicolons
  5. Parentheses
  6. Variables
  7. Strings
  8. Arrays
  9. Functions
  10. Strict Mode
  11. Arguments
  12. Regular Expressions
  13. Blocks
  14. Equality
  15. Errors
  16. Comments
  17. Naming
  18. This
  19. Classes
  20. Setters and Getters
  21. Method Chaining
  22. Documentation
  23. Performance
  24. Modularity
  25. Client-side JavaScript
  26. Dependencies
  27. Versioning
  28. Additional Resources
  29. License

Intro

Always abide by the Law of Code Style Consistency, or, in other words, when in Rome, do as the Romans do.

While the code base to which you want to contribute may be a horrific mess in terms of aesthetic appearance and style, style consistency takes precedent over personal preference and canon. The more consistent a code base is in terms of style, the more readers of the code can focus on what the code does rather than deciphering changes in style.

So, even if your peers commit various faux pas outlined below, as long as you are contributing to their code base, abide by their conventions.

A code base--module, repository, application, library, etc--should always appear to have a single author and not be a schizophrenic franken-mess.

This stated, for those opportunities where you are the primary author, you should lead by example and write clean, readable, and testable code.

Hopefully, most of the conventions outlined below will help enable you to do so.

General Principles

  • Prefer standards to non-standards.
  • Do one thing and do one thing well.
  • Keep your code clean. Create feature branches for experimental development, extensive annotations, and/or alternative implementations.

Whitespace

  • Use tab indentation. Tab indentation allows the developer to specify the space indentation equivalent in her editor. For example, in Sublime Text, you can specify in your user preferences

    "tab_width": 4
    

    Alternatively, use an .editorconfig file in conjunction with IDE (sublime, atom) and/or browser (chrome) plugins.

  • Even if you must use spaces, never mix tabs and spaces. This is formatting hell, as a simple find-and-replace is useless in the face of such chaos.

  • Always include 1 space before a leading brace.

    // Do:
    function query() {
    	// Do something...
    }
    
    // Don't:
    function query(){
    	// Do something...
    }
  • Do include 1 space before and after arguments for readability.

    // Do:
    function test( arg1, arg2, arg3 ) {
    	// Do something...
    }
    
    // Don't:
    function test(arg1,arg2,arg3) {
    	// Do something...
    }
  • In general, include 1 space before and after array indices (when readability is maintained).

    // Do:
    var foo = bar[ 10 ];
    
    // Don't:
    var foo = bar[10];
  • Use discretion when using spaces around array indices buried in braces.

    // Okay:
    var foo = myFunction( ( a === b ) ? bar[0] : bar[1] ) );
  • In general, include 1 space before and after operators.

    // Do:
    var a = 1 + 1;
    
    // Don't:
    var a=1+1;
  • Use discretion when operators are contained within complex expressions and string concatenation.

    // Okay:
    var str = 'This is a long string by '+firstName+' '+lastName+', which may keep going and going and...';
    
    // Okay:
    var x = (x+y+z)*(t-w-v) + 5;
  • Do not include a space before or after unary operators.

    // Do:
    x = ++y;
    z = z++;
    
    // Don't:
    x = ++ y;
    z = z ++;
  • Always include 1 space after comment marks.

    // Do:
    
    // This is a single-line comment.
    
    /**
    * This is a multi-
    * line comment.
    */
    
    
    // Don't:
    
    //This is a single-line comment.
    
    /**
    *This is a mult-
    *line comment.
    */
  • Do not include space indention in your multi-line comments. Some IDEs have a tendency to auto-indent based on the previous line, thus pushing all subsequent lines 1 character to the right.

    // Do:
    
    /**
    * This is a multi-line comment.
    * The comment continues and continues...
    * ...until it no longer does.
    */
    
    
    // Don't:
    
    /**
     * This is a multi-line comment.
     * The comment continues and continues...
     * ...until it no longer does.
     */
  • Do indent when using method chaining.

    // Do:
    var svg = d3.select( '.main' )
    	.append( 'svg:svg' )
    		.attr( 'class', 'canvas' )
    		.attr( 'data-id', Date.now() )
    		.attr( 'width', 100 )
    		.attr( 'height', 100 );
    
    // Don't:
    var svg = d3.select( '.main' ).append( 'svg:svg' ).attr( 'class', 'canvas' ).attr( 'data-id', Date.now() ).attr( 'width', 100 ).attr( 'height', 100 );
  • In general, do not introduce newlines between conditions.

    // Do:
    if ( foo === bar ) {
    	// Do something...
    } else {
    	// Do something different...
    }
    
    // Don't:
    if ( foo === bar ) {
    	// Do something...
    }
    else {
    	// Do something different...
    }
  • Use discretion when faced with multiple conditions.

    // Do:
    if ( foo === bar ) {
    	// Do something...
    } else if ( foo === beep ) {
    	// Do something else...
    } else if ( bar === bar ) {
    	// Do something more...
    } else {
    	// Do something different...
    }
    
    // Okay:
    if ( foo === bar ) {
    	// Do something...
    }
    else if ( foo === beep ) {
    	// Do something else...
    }
    else if ( baz === bar ) {
    	// Do something more...
    }
    else {
    	// Do something different...
    }
  • Do not indent the case keyword within switch statements.

    // Do:
    switch( foo ) {
    case 'bar':
    	// Do something...
    	break;
    case 'beep';
    	// Do something...
    	break;
    default:
    	// Do something...
    }
    
    // Don't:
    switch( foo ) {
    	case 'bar':
    		// Do something...
    		break;
    	case 'beep';
    		// Do something...
    		break;
    	default:
    		// Do something...
    }

Semicolons

  • Use semicolons. While semicolons are not required in most cases due to automatic semicolon insertion, prefer to be explicit in specifying when a statement ends.

    // Do:
    if ( foo === bar ) {
    	return true;
    }
    
    // Don't:
    if ( foo === bar ) {
    	return true
    }

Parentheses

  • Do include parentheses to visually reinforce order of operations.

    // Do:
    var a = ( b * c ) + 5;
    
    // Don't:
    var a = b * c + 5;
  • Do include parentheses around the test condition in ternary operators.

    // Do:
    var foo = ( a === b ) ? a*3 : b/4;
    
    // Don't:
    var foo = a === b ? a*3 : b/4;

Variables

  • Do declare variables at the top of their scope. Doing so makes variable hoisting explicit.

    // Do:
    function myFunction() {
    	var foo = 3,
    		bar;
    
    	if ( foo ) {
    		// Do something...
    	}
    	bar = foo * 5;
    }
    
    // Okay:
    function myFunction() {
    	var foo = 3;
    	var bar;
    
    	if ( foo ) {
    		// Do something...
    	}
    	bar = foo * 5;
    }
    
    // Don't:
    function myFunction() {
    	var foo = 3;
    
    	if ( foo ) {
    		// Do something...
    	}
    	var bar = foo * 5;
    }
  • Do not use leading commas when declaring multiple variables.

    // Do:
    var boop = 'hello',
    	beep = false,
    	bar = null,
    	foo = 3;
    
    // Don't:
    var boop = 'hello'
      , beep = false
      , bar = null
      , foo = 3;
  • Do declare assigned variables first.

    // Do:
    var bar = null,
    	foo = 3,
    	beep,
    	boop;
    
    // Don't:
    var beep, boop,
    	foo = 3,
    	bar = null;
  • Prefer using a single var statement to declare multiple variables.

    // Do:
    var boop = 'hello',
    	beep = false,
    	bar = null,
    	foo = 3;
    
    // Okay:
    var boop = 'hello';
    var beep = false;
    var bar = null;
    var foo = 3;
  • In general, prefer declaring variables on separate lines.

    // Do:
    var beep,
    	boop,
    	bop,
    	bap,
    	i;
  • Use discretion when declaring 2 or fewer variables.

    // Okay:
    var out, err;
    function beep() {
    	// Do something...
    }
  • Prefer grouping related variables on the same line.

    // Do:
    var boop = 'hello',
    	out,
    	i, j, k; // => iteration vars

Strings

  • Always use single quotes for strings.

    // Do:
    var str = 'Hello';
    
    // Don't:
    var str = "Hello";

Arrays

  • In general, use array literal syntax.

    // Do:
    var arr = [];
    
    // Don't:
    var arr = new Array();
  • Do instantiate a new array when you know the array length and the array length is less than 64K elements.

    // Do:
    var arr = new Array( 100 );
    for ( var i = 0; i < arr.length; i++ ) {
    	arr[ i ] = Math.random();
    }
    
    // Don't:
    var arr = [];
    for ( var i = 0; i < 100; i++ ) {
    	arr.push( Math.random() );
    }
  • To convert an array-like object to an array, use a for loop.

    // Do:
    var nargs = arguments.length,
    	args = new Array( nargs );
    
    for ( var i = 0; i < nargs; i++ ) {
    	args[ i ] = arguments[ i ];
    }
    
    // Don't:
    var args = Array.prototype.slice.call( arguments );
  • When copying an array, for small arrays use a for loop; for large arrays, use Array#slice().

    var arr,
    	out,
    	i;
    
    // Small arrays...
    arr = new Array( 10 );
    out = new Array( arr.length );
    for ( i = 0; i < arr.length; i++ ) {
    	out[ i ] = arr[ i ];
    }
    
    // Large arrays...
    arr = [];
    for ( i = 0; i < 1e6; i++ ) {
    	arr[ i ] = Math.random();
    }
    out = arr.slice();

Objects

  • Do split object properties over multiple lines.

    // Do:
    var obj;
    
    obj = {
    	'a': null,
    	'b': 5,
    	'c': function() {
    		return true;
    	},
    	'd': ( foo === bar ) ? foo : bar
    };
    
    // Don't:
    var obj = { 'a': null, 'b': 5, 'c': function() { return true; }, 'd': ( foo === bar ) ? foo : bar };
  • Do not align object values.

    // Do:
    var obj = {
    	'prop': true,
    	'attribute': 'foo',
    	'name': 'bar'
    };
    
    // Don't:
    var obj = {
    	'prop'     : true,
    	'attribute': 'foo',
    	'name'     : 'bar'
    };
  • Do not include a trailing comma.

    // Do:
    var obj = {
    	'prop': true,
    	'attribute': 'foo',
    	'name': 'bar'
    };
    
    // Don't:
    var obj = {
    	'prop': true,
    	'attribute': 'foo',
    	'name': 'bar', // <= DON'T
    };

Functions

  • In general, do declare functions using function statements, rather than function expressions. This (1) avoids problems encountered due to hoisting and (2) minimizes the use of anonymous functions.

    // Do:
    function beep() {
    	console.log( 'boop' );
    }
    
    // Don't:
    var beep = function() {
    	console.log( 'boop' );
    }
  • Do minimize closures and declare functions at the highest possible scope.

    // Do:
    function beep() {
    	boop();
    }
    
    function boop() {
    	// Do something...
    }
    
    // Don't:
    function beep() {
    	boop();
    	function boop() {
    		// Do something...
    	}
    }
  • Do not declare functions inside for loops and conditions.

    // Do:
    function beep( idx, clbk ) {
    	clbk( 'beep'+i );
    }
    function bop( msg ) {
    	console.log( msg );
    }
    
    for ( var i = 0; i < 10; i++ ) {
    	beep( i, bop );
    }
    
    // Don't:
    for ( var i = 0; i < 10; i++ ) {
    	beep( i, function bop( msg ) {
    		console.log( msg );
    	});
    }
    
    // Do:
    function onTimeout( idx ) {
    	return function onTimeout() {
    		console.log( idx );
    	};
    }
    for ( var i = 0; i < 10; i++ ) {
    	setTimeout( onTimeout( i ), 1000 );
    }
    
    // Don't:
    for ( var i = 0; i < 10; i++ ) {
    	setTimeout( function onTimeout() {
    		console.log( i );
    	}, 1000 );
    }
    
    // Do:
    function bap() {
    	// Do something...
    }
    if ( i < 11 ) {
    	bap();
    }
    
    // Don't:
    if ( i < 11 ) {
    	bap();
    	function bap() {
    		// Do something...
    	}
    }
  • Do place parentheses around immediately invoked function expressions (IIFE). Make a clear distinction between a function declaration and one that is immediately invoked.

    // Do:
    (function init() {
    	// Do something...
    })();
    
    // Don't:
    function init() {
    	// Do something...
    }();
  • Do place enclosed functions below the return statement.

    // Do:
    function getEquation( a, b, c ) {
    	var d;
    
    	a = 3 * a;
    	b = a / 5;
    	c = Math.pow( b, 3 );
    
    	d = a + ( b / c );
    
    	return eqn;
    
    	/**
    	* FUNCTION: eqn( e )
    	*	Computes a complex equation.
    	*
    	* @param {Number} e - dynamic value
    	* @returns {Number} equation output
    	*/
    	function eqn( e ) {
    		return e - d + ( 15 * a ) + ( Math.pow( b, 1 / c ) );
    	}
    } // end FUNCTION getEquation()
  • Prefer primitive expressions over their functional counterparts. Unnecessary function calls introduce additional overhead.

    var squared = new Array( arr.length );
    
    // Do:
    for ( var i = 0; i < arr.length; i++ ) {
    	squared[ i ] = arr[ i ] * arr[ i ];
    }
    
    // Don't:
    squared = arr.map( function( value ) {
    	return value * value;
    });
  • Asynchronous callbacks requiring error handling should have an error parameter as their first argument. If no errors, error should be set to null.

    // Do:
    function clbk( error, value ) {
    	if ( error ) {
    		return;
    	}
    	console.log( value );
    }
    
    function onResponse( error, response, body ) {
    	if ( error ) {
    		clbk( error );
    		return;
    	}
    	clbk( null, body );
    }
    
    request({
    	'method': 'GET',
    	'uri': 'http://127.0.0.1'
    }, onResponse );
  • Prefer closures and function factories rather than nested functions and callbacks.

    // Do:
    function mult( x, y ) {
    	return x * y;
    }
    function cube( value ) {
    	var v;
    	v = mult( value, value );
    	v = mult( v, value );
    	return v;
    }
    function compute( value ) {
    	return function compute() {
    		return cube( value );
    	};
    }
    function deferredComputation( value ) {
    	return compute( value );
    }
    
    // Don't:
    function deferredComputation( value ) {
    	return compute;
    	function compute() {
    		return cube();
    		function cube() {
    			var v;
    			v = mult( value, value );
    			v = mult( v, value );
    			return v;
    			function mult( x, y ) {
    				return x * y;
    			}
    		}
    	}
    }

Strict Mode

  • Always write JavaScript in strict mode. Doing so discourages bad practices, avoids silent errors, and can result in better performance, as the compiler can make certain assumptions about the code.

    'use strict';
    
    NaN = null; // throws an Error
  • Prefer strict mode for a whole script. If not possible, use strict mode for each available function.

    function beep() {
    	'use strict';
    	delete Object.prototype; // throws an Error
    }

Arguments

  • Never pass the arguments variable to another function. Doing so automatically puts the function in optimization hell.

    // Do:
    function fcn() {
    	var nargs = arguments.length,
    		args = new Array( nargs ),
    		out,
    		i;
    
    	for ( i = 0; i < nargs; i++ ) {
    		args[ i ] = arguments[ i ];
    	}
    	out = foo( args );
    }
    
    // Don't:
    function fcn() {
    	var out = foo( arguments );
    }
  • Always reassign input arguments to new variables when mentioning arguments in a function body. Recycling variables when mentioning arguments in a function body prevents compiler optimization.

    // Do:
    function fcn( value, options ) {
    	var opts,
    		err;
    
    	if ( arguments.length < 2 ) {
    		opts = value;
    	} else {
    		opts = options;
    	}
    	err = validate( opts );
    	if ( err ) {
    		throw err
    	}
    	...
    }
    
    // Don't:
    function fcn( value, options ) {
    	var err;
    	if ( arguments.length < 2 ) {
    		options = value;
    	}
    	err = validate( options );
    	if ( err ) {
    		throw err
    	}
    	...
    }

Regular Expressions

  • Do assign regular expressions to variables rather than using them inline.

    // Do:
    var regex = /\.+/;
    
    beep();
    
    function beep( str ) {
    	if ( regex.test( str ) ) {
    		// Do something...
    	}
    }
    
    // Don't:
    beep();
    
    function beep( str ) {
    	if ( /\.+/.test( str ) ) {
    		// Do something...
    	}
    }
  • Always document regular expressions.

    /**
    * REGEX: /^\/((?:\\\/|[^\/])+)\/([imgy]*)$/
    *	Matches parts of a regular expression string.
    *
    *	/^\/
    *		-	match a string that begins with a /
    *	()
    *		-	capture
    *	(?:)+
    *		-	capture, but do not remember, a group of characters which occur 1 or more times
    *	\\\/
    *		-	match the literal \/
    *	|
    *		-	OR
    *	[^\/]
    *		-	anything which is not the literal \/
    *	\/
    *		-	match the literal /
    *	([imgy]*)
    *		-	capture any characters matching `imgy` occurring 0 or more times
    *	$/
    *		-	string end
    */
    var re = /^\/((?:\\\/|[^\/])+)\/([imgy]*)$/;

Blocks

  • Always use curly braces. Not using them is a common source of bugs.

    // Do:
    if ( foo === bar ) {
    	return true;
    }
    
    // Don't:
    if ( foo === bar ) return true;
  • Always place the leading brace on the same line.

    // Do:
    if ( foo === bar ) {
    	// Do something...
    }
    
    function query() {
    	// Do something...
    }
    
    // Don't:
    if ( foo === bar )
    {
    	// Do something...
    }
    
    function query()
    {
    	// Do something...
    }
  • Do early return.

    // Do:
    function foo( value ) {
    	if ( value === 'bar' ) {
    		return 'Hello';
    	}
    	return 'Goodbye';
    }
    
    // Don't:
    function foo( value ) {
    	var str;
    	if ( value === 'bar' ) {
    		str = 'Hello';
    	} else {
    		str = 'Goodbye';
    	}
    	return str;
    }
  • Do early continue.

    // Do:
    for ( var i = 0; i < 10; i++ ) {
    	if ( i === 5 ) {
    		continue;
    	}
    	// Do something...
    }
    
    // Don't:
    for ( var i = 0; i < 10; i++ ) {
    	if ( i !== 5 ) {
    		// Do something...
    	}
    }

Equality

  • Always prefer === and !== to == and !=. Not enforcing type equality is a source of bugs.

    // Do:
    if ( foo === bar ) {
    	// Do something...
    }
    
    // Don't:
    if ( foo != bar ) {
    	// Do something...
    }

Errors

  • Always provide descriptive error messages.

    // Do:
    var err = new TypeError( 'invalid input argument. Window option must be a positive integer. Value: `' + value + '`.' );
    
    // Don't:
    var err = new Error( '1' );
  • Always fail fast (see programmer errors). A library should throw and provide tailored error messages if expected conditions are not met. Doing so facilitates debugging and eases code maintenance.

    // Do:
    /**
    * @api public
    */
    function beep( clbk ) {
    	if ( !arguments.length ) {
    		throw new Error( 'insufficient input arguments. Must provide a callback function.' );
    	}
    }
    
    // Don't:
    /**
    * @api public
    */
    function boop( clbk ) {
    	clbk();
    }
  • For public facing library APIs, always validate input values. Doing so makes contracts explicit, facilitates testing, and helps mitigate the presence of subtle bugs.

    // Do:
    function foo( opts ) {
    	if ( !isObject( opts ) ) {
    		throw new TypeError( 'invalid input argument. Options argument must be an object. Value: `' + opts + '`.' );
    	}
    }
    
    // Don't:
    function bar( opts ) {
    	// What happens if `opts` or `opts.ssl` are not objects???
    	var key = opts.ssl.key;
    }
  • When validating, always include the invalid value in the error message. Doing so makes debugging and logging easier.

    // Do:
    function bop( len ) {
    	if ( !isPositiveInteger( len ) ) {
    		throw new TypeError( 'invalid input argument. Length must be a positive integer. Value: `' + len + '`.' );
    	}
    }
    
    // Don't:
    function bap( len ) {
    	if ( !isPositiveInteger( len ) ) {
    		throw new Error( 'invalid value.' );
    	}
    }
  • Never trap uncaught exceptions without crashing. Not crashing upon encountering an uncaughtException leaves your application in an undefined state and can result in memory leaks.

    // Okay:
    function onError( error ) {
    	console.error( 'Caught exception. Err: %s', error.message );
    	process.exit( 1 ); // <= THIS IS KEY!!!!
    }
    process.on( 'uncaughtException', onError );
    
    // DON'T:
    function onError( error ) {
    	console.error( 'Caught exception. Err: %s', error.message );
    }
    process.on( 'uncaughtException', onError );
  • Always designate the first argument for asynchronous function APIs as an error argument. If no error occurs, the value, if set, should be null. Designing asynchronous APIs in this fashion matches the convention found in Node.js core.

    // Do:
    function goodAsync( clbk ) {
    	setTimeout( done, 1000 );
    	function done() {
    		clbk( null, 'beep' );
    	}
    }
    
    // Don't:
    function badAsync( clbk ) {
    	setTimeout( done, 1000 );
    	function done() {
    		clbk( 'beep' );
    	}
    }
  • Always return appropriate status codes.

    // Do:
    response
    	.status( 502 )
    	.json({
    		'status': 502,
    		'message': 'unable to connect to remote database.'
    	});
    
    // Don't:
    response
    	.send( 200 )
    	.json({
    		'success': false
    	});

Comments

  • Do use /** Comments */ for mult-line comments.

    // Do:
    
    /**
    * FUNCTION: beep()
    *	Beep to go boop.
    */
    function beep() {
    	// Do something...
    }
    
    // Don't:
    
    // FUNCTION: beep()
    //	Beep to go boop.
    function beep() {
    	// Do something...
    }
  • Do use JSDoc and do so for every function. Be sure to include descriptions, parameters, and other information.

    // Do:
    
    /**
    * FUNCTION: transform( str )
    *	String transformer.
    *
    * @param {String} str - string to be transformed.
    * @returns {String} transformed string
    */
    function transform( str ) {
    	return str + ' has been transformed.';
    }
    
    // Don't:
    function transform( str ) {
    	return str + ' has been transformed.';
    }
  • Do use // for single-line comments. Place the comment above the comment subject, and place an empty line above the comment.

    // Do:
    
    // Set the default value to null.
    var foo = bar || null;
    
    
    // Don't:
    var foo = bar || null; // set the default value to null.
  • Use // FIXME: to annotate problems.

    // FIXME: misses the case where value is 0. Want to check if value is not numeric.
    if ( !value ) {
    	return false;
    }
  • Use // TODO: to annotate tasks.

    function Ctor() {
    
    	// TODO: make `name` property value publicly accessible.
    	this.name = 'foobar';
    
    	return this;
    }
  • Use // HACK: to annotate fragile/non-general solutions.

    // HACK: temporary fix; host and port should be abstracted to another module.
    var host = '127.0.0.1',
    	port = 7331;
  • Use // WARNING: to annotate possible gotchas/pitfalls.

    // WARNING: shared reference of a mutable object; possible side effects.
    var a = b = {};
  • Use // NOTE: to annotate questions, comments, or anything which does not fit under TODO/FIXME/HACK/WARNING which should be brought to a user's attention.

    // NOTE: consider optimizing this for large arrays (len > 64K).
    var arr = new Array( len );
    for ( var i = 0; i < len; i++ ) {
    	arr[ i ] = Math.random();
    }
  • Consider commenting closing braces. Doing so helps lessen bracket hell when dealing with long code blocks.

    function longFunction() {
    	
    	// [0] Do first thing.
    	firstThing();
    
    	// [1] Do second thing.
    	secondThing();
    
    	// [2] Do third thing.
    	thirdThing();
    
    	// ...
    
    	// [N-1] Do Nth thing.
    	nthThing(); 
    
    	return true;
    } // end FUNCTION longFunction()

Naming

  • Always use camelCase for functions, objects, instances, and variables.

    // Do:
    function testFunction() {
    	// Do something...
    }
    
    var myObject = {};
    
    var myInstance = new Instance();
    
    
    // Don't:
    function testfunction() {
    	// Do something...
    }
    
    var MyObject = {};
    
    var reallylongvariablename = 0;
  • Always use PascalCase for constructors.

    // Do:
    function RoboRobot() {
    	this.name = 'Beep';
    	return this;
    }
    
    var robo = new RoboRobot();
    
    
    // Don't:
    function roboRobot() {
    	this.name = 'Boop';
    	return this;
    }
    
    var robo = new roboRobot();
  • Always use a leading underscore when naming private properties.

    // Do:
    function Robot() {
    	this._private = true;
    	return this;
    }
    
    // Don't:
    function Robot() {
    	this.__private__ = true;
    	this.private_ = true;
    	return this;
    }
  • Always name all functions. Named functions are easier to find in stack traces and consequently debug.

    // Do:
    function onResponse( error, response, body ) {
    	// Do something...
    }
    
    request({
    	'method': 'GET',
    	'uri': 'http://127.0.0.1'
    }, onResponse );
    
    // Don't:
    request({
    	'method': 'GET',
    	'uri': 'http://127.0.0.1'
    }, function( error, response, body ) {
    	// Do something...
    });
  • Do name constants in all CAPS.

    // Do:
    var VALUE = 3.14;
    
    // Don't:
    var value = 3.14;

This

  • When caching a reference to this, use self.

    // Do:
    function Robot( name ) {
    	var self = this;
    	if ( !(this instanceof Robot) ) {
    		return new Robot( name );
    	}
    	this.name = name;
    	this.greet = greet;
    
    	return this;
    
    	function greet() {
    		return 'Hello! My name is ' + self.name + '.';
    	}
    }
  • Avoid using bind (performance).

    // Do:
    function greet( ctx ) {
    	return function greet() {
    		return 'Hello! My name is ' + ctx.name + '.';
    	};
    }
    
    function Robot() {
    	if ( !(this instanceof Robot) ) {
    		return new Robot();
    	}
    	this.name = 'Beep';
    	this.greet = greet( this );
    	return this;
    }
    
    // Don't:
    function greet() {
    	return this.name;
    }
    
    function Robot() {
    	var fcn;
    	if ( !(this instanceof Robot) ) {
    		return new Robot();
    	}
    	this.name = 'Beep';
    	this.greet = greet.bind( this );
    	return this;
    }

Classes

  • Always allow for classes to be instantiated without the new operator.

    // Do:
    function Robot() {
    	if ( !(this instanceof Robot) ) {
    		return new Robot();
    	}
    	return this;
    }
    
    // Don't:
    function Robot() {
    	return this;
    }

Setters and Getters

  • Where appropriate, combine set/get into a single method.

    // Do:
    Robot.prototype.name = function( name ) {
    	if ( !arguments.length ) {
    		return this._name;
    	}
    	if ( typeof name !== 'string' ) {
    		throw new Error( 'invalid input value. Name must be a string. Value: `' + name + '`.' );
    	}
    	this._name = name;
    	return this;
    }
    
    // Don't:
    Robot.prototype.setName = function( name ) {
    	if ( typeof name !== 'string' ) {
    		throw new Error( 'invalid input value. Name must be a string. Value: `' + name + '`.' );
    	}
    	this._name = name;
    	return this;
    }
    
    Robot.prototype.getName = function() {
    	return this._name;
    }
  • For public libraries, do type and sanity check input arguments. While checks do incur computational cost, not providing such checks can entail a considerable drain on a developer's time. Subtle bugs can arise from using unexpected types. Be explicit in what you expect and write tests confirming your expectations. Your stringency helps other developers debug their own code.

    // Do:
    Stream.prototype.window = function( win ) {
    	if ( !arguments.length ) {
    		return this._window;
    	}
    	if ( typeof win !== 'number' ||  win !== win ) {
    		throw new Error( 'invalid input argument. Window size must be numeric. Value: `' + win + '`.' );
    	}
    	if ( Math.floor( win ) !== win || win <= 0 ) {
    		throw new Error( 'invalid input argument. Window size must be a positive integer. Value: `' + win + '`.' );
    	}
    	this._window = win;
    	return this;
    }
    
    // Don't:
    Stream.prototype.window = function( win ) {
    	if ( !arguments.length ) {
    		return this._window;
    	}
    	this._window = win;
    }

Method Chaining

  • Return this to enable method chaining and to create a fluent interface.

    function Robot() {
    	if ( !(this instanceof Robot) ) {
    		return new Robot();
    	}
    	this._name = '';
    	this._color = 'black';
    	return this;
    }
    
    Robot.prototype.name = function( name ) {
    	if ( !arguments.length ) {
    		return this._name;
    	}
    	if ( typeof name !== 'string' ) {
    		throw new Error( 'invalid input value.' );
    	}
    	this._name = name;
    	return this;
    }
    
    Robot.prototype.color = function( color ) {
    	if ( !arguments.length ) {
    		return this._color;
    	}
    	if ( typeof color !== 'string' ) {
    		throw new Error( 'invalid input value.' );
    	}
    	this._color = color;
    	return this;
    }
    
    var robo = new Robot();
    
    robo.name( 'Robo' )
    	.color( 'pink' );

Documentation

  • Always.

  • Prefer too much to too little.

    // Do:
    
    /**
    * FUNCTION: autocorr( vector )
    *	Given an input data vector, calculate its auto-correlation. To calculate the auto-correlation using an FFT, the data is padded to have length 2^n, where `n` is the next power of 2 greater than the vector length. For more details, consult {@link http://example.com}.
    *
    * @param {Number[]} vector - 1d array
    * @returns {Number} auto-correlation
    */
    function autocorr( vector ) {
    	// Calculate...
    }
    
    
    // Don't:
    /**
    * FUNCTION: autocorr( vector )
    *	Calculates auto-correlation.
    */
    function autocorr( vector ) {
    	// Calculate...
    }
  • For client-side JavaScript, if you are concerned about file size, build/include a distributable file, stripped of comments and minified. Keep your source annotated.

  • Always include example/demo code that is easily runnable.

  • Do not claim that your code is self-documenting. Your code is not. Period.

  • Do not rely on tests as your sole source of documentation. While tests are documentation, annotating your source provides greater insight and a means to explain why you made particular design choices.

  • Always make your documentation beautiful. Take as much pride in your documentation as you do in your code.


Performance

  • Prefer simplicity and readability over performance optimization. For example,

    // Do:
    x = Math.floor( x );
    
    // Don't: (avoid using a bitshift unless you really need to)
    x >> 0;
  • Take JSPerf tests with a grain of salt, as results can vary significantly from browser to browser and across browser versions.


Modularity

  • Every file within a Node module should be less than 200 lines of code. The only exceptions are tests files, which are generally 2-3x the length of the files they test. If a file is longer than 200 lines, the code is undoubtedly too complex, not maintainable, hard to test, and needs to be refactored into smaller sub-modules. Ideally, an individual file should never be longer than 80 lines.
  • Prefer only 1 function per file. A file which contains fewer functions is easier to test, read, and maintain. This is particularly true for Node modules.
  • Always bear in mind the single responsibility principle.
  • Always strive for reusability.

Client-side JavaScript

  • Forgo dependence on monolithic libraries, such as jQuery, and use native JavaScript equivalents for DOM manipulation. Relying on such libraries leads to code bloat.

    // Do:
    var el = document.querySelector( '#main' );
    
    // Don't:
    var el = $( '#main' );
  • Always wrap client-side scripts in immediately invoked function expressions (IIFE). Doing so prevents variable leakage.

    // Do:
    (function() {
    	'use strict';
    
    	var beep = 'boop';
    	...
    })();
  • Always namespace client-side global variables. Doing so helps minimize global variable name collisions.

    // Do:
    var myApp = {};
    myApp.name = 'App';
    myApp.start = function start(){};
    
    window.myApp = myApp;
    
    // Don't:
    window.start = function start(){};
    window.name = 'App';

Dependencies

  • Avoid using large (swiss-army knife type) dependencies when only a small subset of functionality is used. In particular, avoid the following libraries:

    • underscore
    • lodash
    • async

    Often smaller, more focused modules are available which can accomplish the same tasks. In general, be explicit in what you require.

  • Always adequately vet any dependencies used. While Github stars and downloads are rough indicators, place more emphasis on the following:

    • Code quality
      • conciseness
      • maintainability
    • Documentation
      • APIs
      • examples
    • Test cases

    For most cases, do not place much weight on how recently the module was updated. Small, focused, well-written modules should not require much updating.

  • Consider maintaining module and developer whitelists relevant to the application domain.


Versioning

  • When creating modules, always use semantic versioning (semver) and adhere to its conventions: MAJOR.MINOR.PATCH.

    // Do:
    {
    	"version": "1.23.5"
    }
    
    // Don't:
    // filename: script_hmm_takes_thingy_and_makes_another_thingy_2000-01-01_version12_updated.js

Additional Resources

Author

Athan Reines (@kgryte). 2014-2015.

License

MIT license.

About

JavaScript style guide.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published