A mostly reasonable approach to JavaScript
- Types
- Objects
- Arrays
- Strings
- Functions
- Properties
- Variables
- Operators
- Hoisting
- Conditional Expressions & Equality
- Blocks
- Comments
- Whitespace
- Leading Commas
- Semicolons
- Type Casting & Coercion
- Naming Conventions
- Accessors
- Constructors
- Modules
- jQuery
- ES5 Compatability
- Testing
- Performance
- Resources
-
Primitives: When you access a primitive type you work directly on its value
stringnumberbooleannullundefined
var foo = 1; var bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
-
Complex: When you access a complex type you work on a reference to its value
objectarrayfunction
var foo = [1, 2]; var bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
-
Use the literal syntax for object creation.
// bad var item = new Object(); // good var item = {};
-
Don't use reserved words as keys.
// bad var superman = { class: 'superhero', default: { clark: kent }, private: true }; // good var superman = { klass: 'superhero', defaults: { clark: kent }, hidden: true };
-
Use the literal syntax for array creation
// bad var items = new Array(); // good var items = [];
-
When you are managing array length use direct assignment over Array#push. jsPerf
var hundredOdds = []; // bad for (var i = 0; i < 100; i++) { hundredOdds.push(i * 2 + 1); } // bad (~6x slower) theArray.slice(0); // good for (var i = 0; i < 100; i++) { hundredOdds[i] = i * 2 + 1; }
-
If you don't know array length use Array#push.
var someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');
-
When you need to copy an array use Array() constructor. jsPerf
var len = items.length; var itemsCopy = []; // bad for (var i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good itemsCopy = Array.apply(null, items);
-
Use single quotes
''for strings// bad var name = "Bob Parr"; // good var name = 'Bob Parr'; // bad var fullName = "Bob" + this.lastName; // good var fullName = 'Bob' + this.lastName;
-
Strings longer than 80 characters should be written across multiple lines using string concatenation.
// bad var errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; // bad var errorMessage = 'This is a super long error that \ was thrown because of Batman. \ When you stop to think about \ how Batman had anything to do \ with this, you would get nowhere \ fast.'; // good var errorMessage = 'This is a super long error that ' + 'was thrown because of Batman.' + 'When you stop to think about ' + 'how Batman had anything to do ' + 'with this, you would get nowhere ' + 'fast.';
-
When programatically building up a string, use Array#join instead of string concatenation. Mostly for IE: jsPerf.
var items; var messages = [{ state: 'success', message: 'This one worked.' },{ state: 'success', message: 'This one worked as well.' },{ state: 'error', message: 'This one did not work.' }]; var length = messages.length; // bad function inbox(messages) { items = '<ul>'; for (var i = 0; i < length; i++) { items += '<li>' + messages[i].message + '</li>'; } return items + '</ul>'; } // good function inbox(messages) { items = []; for (var i = 0; i < length; i++) { items[i] = messages[i].message; } return '<ul><li>' + items.join('</li><li>') + '</li></ul>'; }
-
Function expressions:
// anonymous function expression var anonymous = function() { return true; }; // named function expression var named = function named() { return true; }; // immediately-invoked function expression (IIFE) (function() { console.log('Welcome to the Internet. Please follow me.'); })();
-
Error is always the first argument in callbacks. This will ensure the user deals with it.
// bad function writeCsvFile(target, data, callback) { convertToCsv(data, function (err, csv) { return callback( csv, target, err); }); }
// good
function writeCsvFile(target, data, callback) {
convertToCsv(data, function (err, csv) {
return callback(err, csv, target);
});
}
```
-
Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead. Browsers will allow you to do it, but they all interpret it differently, which is bad news bears.
// bad if (currentUser) { function test() { console.log('Nope.'); } } // good if (currentUser) { var test = function test() { console.log('Yup.'); }; }
-
Return on callback
// bad function writeCsvFile(target, data, callback) { convertToCsv(data, function (err, csv) { if (err) { callback(err); // oops! no return } // this line gets called even when theres an error writeFile(target, csv, callback); }); } // good function writeCsvFile(target, data, callback) { convertToCsv(data, function (err, csv) { if (err) { return callback(err); // execution stops here } writeFile(target, csv, callback); }); }
-
Never name a parameter
arguments, this will take precendence over theargumentsobject that is given to every function scope.// bad function nope(name, options, arguments) { // ...stuff... } // good function yup(name, options, args) { // ...stuff... }
-
No Nested closures
// bad setTimeout(function() { client.connect(function() { console.log('losing'); }); }, 1000); // good setTimeout(function() { client.connect(afterConnect); }, 1000); function afterConnect() { console.log('winning'); }
-
Use dot notation when accessing properties.
var luke = { jedi: true, age: 28 }; // bad var isJedi = luke['jedi']; // good var isJedi = luke.jedi;
-
Use subscript notation
[]when accessing properties with a variable.var luke = { jedi: true, age: 28 }; function getProp(prop) { return luke[prop]; } var isJedi = getProp('jedi');
-
Always use
varto 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
vardeclaration requiring modules at the top of a file and declare each variable comma first and on a newline. Declare sub-selections of module after all modules have been required.// bad var io = require('socket.io'); var mm = require('./my-module').init(); var myProp = mm.myProperty; var express = require('express'); // good var io = require('socket.io') , mm = require('./my-module').init() , express = require('express') , myProp = mm.myProperty;
-
For all other variables use
varto declare every variable to ovoid accidental global vars or messy syntax.// bad var items = getItems(), goSportsTeam = true, dragonball = 'z'; // good var items = getItems(); var goSportsTeam = true; var dragonball = 'z';
-
Declare unassigned variables last. This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables. Always set the variables to there anticipated type or
nullif you are unsure.// bad var i, len, dragonball, items = getItems(), goSportsTeam = true; // bad var i, items = getItems(), dragonball; var goSportsTeam = true, len; // good var items = getItems(); var goSportsTeam = true; var dragonball = ''; var i = null; var length = 0;
-
Assign variables at the top of their scope. This helps avoid issues with variable declaration and assignment hoisting related issues. If scope spans a large amount of lines try to keep variables close to where they are used.
// bad function() { test(); console.log('doing stuff..'); //..other stuff.. var name = getName(); if (name === 'test') { return false; } return name; } // good function() { var name = getName(); test(); console.log('doing stuff..'); //..other stuff.. if (name === 'test') { return false; } return name; } // bad function() { var name = getName(); if (!arguments.length) { return false; } return true; } // good function() { if (!arguments.length) { return false; } var name = getName(); return true; }
-
Always use the === operator for checking equality.
// bad var a = 0; if (a == '') { console.log('losing'); } // good var a = 0; if (a === '') { console.log('winning'); }
-
Always use the !== operator for checking inequality.
// bad var a = 0; if (a != '') { console.log('losing'); } // good var a = 0; if (a !== '') { console.log('winning'); }
-
For more information refer to Why Use Triple Equals JS
-
Use
===and!==over==and!=and use!!when testing a single variable or object length to ensure boolean value. -
Conditional expressions are evaluated using coercion with the
ToBooleanmethod and always follow these simple rules:- Objects evaluate to true
- Undefined evaluates to false
- Null evaluates to false
- Booleans evaluate to the value of the boolean
- Numbers evalute to false if +0, -0, or NaN, otherwise true
- Strings evaluate to false if an empty string
'', otherwise true
if (!![0]) { // true // An array is an object, objects evaluate to true }
-
Use shortcuts.
// bad if (name !== '') { // ...stuff... } // good // use `!!` to ensure boolean value if (!!name) { // ...stuff... } // bad if (collection.length > 0) { // ...stuff... } // good // use `!!` to ensure boolean value if (!!collection.length) { // ...stuff... }
-
For more information see Truth Equality and JavaScript by Angus Croll
-
Always use braces with blocks.
// bad if (test) return false; // good if (test) { return false; } // bad function() { return false; } // good function() { return false; }
-
Use
/** ... */for multiline comments. Include a description, specify types and values for all parameters and return values. Always create doc blocks for functions so documentation can be generated.// bad // make() returns a new element // based on the passed in tag name // // @param <String> tag // @return <Element> element function make(tag) { // ...stuff... return element; } // good /** * make() returns a new element * based on the passed in tag name * * @param <String> tag * @return <Element> element */ function make(tag) { // ...stuff... return element; }
-
Use
//for single line comments. Place single line comments on a newline above the subject of the comment. Put an emptyline before the comment.// bad var active = true; // is current tab // good // is current tab var active = true; // bad function getType() { console.log('fetching type...'); // set the default type to 'no type' var type = this._type || 'no type'; return type; } // good function getType() { console.log('fetching type...'); // set the default type to 'no type' var type = this._type || 'no type'; return type; }
-
Use soft tabs (aka spaces) set to 2 spaces
// bad function() { ∙∙∙∙var name; } // bad function() { ∙var name; } // good function() { ∙∙var name; }
-
Place 1 space before the leading brace.
// bad function test(){ console.log('test'); } // good function test() { console.log('test'); } // bad dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog' }); // good dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog' });
-
Place an empty newline at the end of the file.
// bad (function(global) { // ...stuff... })(this);
// good (function(global) { // ...stuff... })(this);
-
Use indentation when making long method chains.
// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();
// good
$('#items')
.find('.selected')
.highlight()
.end()
.find('.open')
.updateCount();
// bad
var leds = stage.selectAll('.led').data(data).enter().append("svg:svg").class('led', true)
.attr('width', (radius + margin) * 2).append("svg:g")
.attr("transform", "translate(" + (radius + margin) + "," + (radius + margin) + ")")
.call(tron.led);
// good
var leds = stage.selectAll('.led')
.data(data)
.enter().append("svg:svg")
.class('led', true)
.attr('width', (radius + margin) * 2)
.append("svg:g")
.attr("transform", "translate(" + (radius + margin) + "," + (radius + margin) + ")")
.call(tron.led);-
Sometimes to increase readability.
// bad var io = require('socket.io'), express = require('express'), bodhi = require('./bodhi/dev'); // good var io = require('socket.io') , express = require('express') , bodhi = require('./bodhi/dev'); // bad var hero = { firstName: 'Bob' , lastName: 'Parr' , heroName: 'Mr. Incredible' , superPower: 'strength' }; // good var hero = { firstName: 'Bob', lastName: 'Parr', heroName: 'Mr. Incredible', superPower: 'strength' };
-
Always.
// bad (function() { var name = 'Skywalker' return name })() // good (function() { var name = 'Skywalker'; return name; })(); // good ;(function() { var name = 'Skywalker'; return name; })();
-
Perform type coercion at the beginning of the statement.
-
Strings:
// => this.reviewScore = 9; // bad var totalScore = this.reviewScore + ''; // good var totalScore = '' + this.reviewScore; // bad var totalScore = '' + this.reviewScore + ' total score'; // good var totalScore = this.reviewScore + ' total score';
-
Use
parseIntfor Numbers and always with a radix for type casting. -
If for whatever reason you are doing something wild and
parseIntis 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
thisuseself.// bad function() { var self = this; return function() { console.log(self); }; } // bad function() { var that = this; return function() { console.log(that); }; } // good function() { var self = this; return function() { console.log(self); }; }
-
Name your functions. This is helpful for stack traces.
// bad var log = function(msg) { console.log(msg); }; // good var log = function log(msg) { console.log(msg); };
-
Accessor functions for properties are not required
-
If you do make accessor functions use getVal() and setVal('hello')
// bad dragon.age(); // good dragon.getAge(); // bad dragon.age(25); // good dragon.setAge(25);
-
If the property is a boolean, use isVal() or hasVal()
// bad if (!dragon.age()) { return false; } // good if (!dragon.hasAge()) { return false; }
-
It's okay to create get() and set() functions, but be consistent.
function Jedi(options) { options || (options = {}); var lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } Jedi.prototype.set = function(key, val) { this[key] = val; }; Jedi.prototype.get = function(key) { return this[key]; };
-
Assign methods to the prototype object, instead of overwriting the prototype with a new object. Overwriting the prototype makes inheritance impossible: by resetting the prototype you'll overwrite the base!
function Jedi() { console.log('new jedi'); } // bad Jedi.prototype = { fight: function fight() { console.log('fighting'); }, block: function block() { console.log('blocking'); } }; // good Jedi.prototype.fight = function fight() { console.log('fighting'); }; Jedi.prototype.block = function block() { console.log('blocking'); };
-
Methods can return
thisto help with method chaining.// bad Jedi.prototype.jump = function() { this.jumping = true; return true; }; Jedi.prototype.setHeight = function(height) { this.height = height; }; var luke = new Jedi(); luke.jump(); // => true luke.setHeight(20) // => undefined // good Jedi.prototype.jump = function() { this.jumping = true; return this; }; Jedi.prototype.setHeight = function(height) { this.height = height; return this; }; var luke = new Jedi(); luke.jump() .setHeight(20);
-
It's okay to write a custom toString() method, just make sure it works successfully and causes no side effects.
function Jedi(options) { options || (options = {}); this.name = options.name || 'no name'; } Jedi.prototype.getName = function getName() { return this.name; }; Jedi.prototype.toString = function toString() { return 'Jedi - ' + this.getName(); };
-
The file should be named with camelCase, live in a folder with the same name, and match the name of the single export.
-
Basic Module
/** * Basic module */ // module deps var Emitter = require('events') , path = require('path'); // main export var Util = module.exports = {}; // internal private state var privateState = 0; // internal public state Util.publicState = 0; /** * [ description] * @return {[type]} [description] */ Util.incModState = function () { privateState = privateState + 1; Util.publicState = Util.pubilicState + 1; }; /** * [privFunc description] * @return {[type]} [description] */ function privFunc(){ // do something }
-
Class Module
/** * Class Module */ var Stream = require('stream'); // expose SampleClass module.exports = SampleClass; // class constructor function SampleClass (opts) { // Allow for instance creation with or without `new` if(!(this instanceof SampleClass)){ return new SampleClass(opts); } // create local var for `this` to use in nested functions var self = this; }; // inherit from module SampleClass.prototype.__proto__ = Stream.prototype; /** * SampleClass Instace Function * @return {[type]} [description] */ SampleClass.prototype.someFunc = function () { var self = this; }; /** * SampleClass Class Function * @return {[type]} [description] */ SampleClass.otherFunc = function () { var x = 10; return x; };
-
Prefix jQuery object variables with a
$.// bad var sidebar = $('.sidebar'); // good var $sidebar = $('.sidebar');
-
Cache jQuery lookups.
// bad function setSidebar() { $('.sidebar').hide(); // ...stuff... $('.sidebar').css({ 'background-color': 'pink' }); } // good function setSidebar() { var $sidebar = $('.sidebar'); $sidebar.hide(); // ...stuff... $sidebar.css({ 'background-color': 'pink' }); }
-
For DOM queries use Cascading
$('.sidebar ul')or parent > child$('.sidebar > .ul'). jsPerf -
Use
findwith 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');
- Refer to Kangax's ES5 compatibility table
-
Yup.
function() { return true; }
- String vs Array Concat
- Try/Catch Cost In a Loop
- Bang Function
- jQuery Find vs Context, Selector
- innerHTML vs textContent for script text
- Loading...
Read This
Other Styleguides
- Google JavaScript Style Guide
- jQuery Core Style Guidelines
- Principles of Writing Consistent, Idiomatic JavaScript
Other Styles
- Naming this in nested functions - Christian Johansen
Books
- JavaScript: The Good Parts - Douglas Crockford
- JavaScript Patterns - Stoyan Stefanov
- Pro JavaScript Design Patterns - Ross Harmes and Dustin Diaz
- High Performance Web Sites: Essential Knowledge for Front-End Engineers - Steve Souders
- Maintainable JavaScript - Nicholas C. Zakas
- JavaScript Web Applications - Alex MacCaw
- Pro JavaScript Techniques - John Resig
- Smashing Node.js: JavaScript Everywhere - Guillermo Rauch
Blogs