An opinionated style guide for writing JavaScript.
- Intro
- General Principles
- Whitespace
- Semicolons
- Parentheses
- Variables
- Strings
- Arrays
- Functions
- Strict Mode
- Arguments
- Regular Expressions
- Blocks
- Equality
- Errors
- Comments
- Naming
- This
- Classes
- Setters and Getters
- Method Chaining
- Documentation
- Performance
- Modularity
- Client-side JavaScript
- Dependencies
- Versioning
- Additional Resources
- License
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.
- 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.
-
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 withinswitch
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... }
-
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 }
-
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;
-
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
-
Always use single quotes for
strings
.// Do: var str = 'Hello'; // Don't: var str = "Hello";
-
In general, use
array
literal syntax.// Do: var arr = []; // Don't: var arr = new Array();
-
Do instantiate a new
array
when you know thearray
length and thearray
length is less than64K
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 afor
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 smallarrays
use afor
loop; for largearrays
, useArray#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();
-
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 };
-
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 anonymousfunctions
.// 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
insidefor
loops andconditions
.// 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 thereturn
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 tonull
.// 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 nestedfunctions
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; } } } }
-
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 }
-
Never pass the
arguments
variable to anotherfunction
. Doing so automatically puts thefunction
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 afunction
body. Recycling variables when mentioningarguments
in afunction
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 } ... }
-
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]*)$/;
-
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... } }
-
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... }
-
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 tailorederror
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 noerror
occurs, the value, if set, should benull
. 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 });
-
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 underTODO
/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()
-
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
. Namedfunctions
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;
-
When caching a reference to
this
, useself
.// 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; }
-
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; }
-
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; }
-
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' );
-
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.
-
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.
- 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 than200
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 than80
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.
-
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';
-
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.
- Code quality
-
Consider maintaining module and developer whitelists relevant to the application domain.
-
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
- Airbnb JavaScript Style Guide
- Idiomatic.js
- Popular Convention
- JavaScript Quality Guide
- Unix Philosophy
- Semantic Versioning
Athan Reines (@kgryte). 2014-2015.