diff --git a/README.md b/README.md index 92f4a2402c..90a58d0163 100644 --- a/README.md +++ b/README.md @@ -16,22 +16,23 @@ 1. [Conditional Expressions & Equality](#conditional-expressions--equality) 1. [Blocks](#blocks) 1. [Comments](#comments) + 1. [Line Length](#line-length) 1. [Whitespace](#whitespace) 1. [Commas](#commas) 1. [Semicolons](#semicolons) + 1. [File Headers](#file-headers) 1. [Type Casting & Coercion](#type-casting--coercion) 1. [Naming Conventions](#naming-conventions) 1. [Accessors](#accessors) 1. [Constructors](#constructors) + 1. [Functional Style](#functional-style) 1. [Events](#events) 1. [Modules](#modules) 1. [jQuery](#jquery) 1. [ECMAScript 5 Compatibility](#ecmascript-5-compatibility) 1. [Testing](#testing) - 1. [Performance](#performance) + 1. [Parting Words](#parting-words) 1. [Resources](#resources) - 1. [In the Wild](#in-the-wild) - 1. [Translation](#translation) 1. [The JavaScript Style Guide Guide](#the-javascript-style-guide-guide) 1. [Contributors](#contributors) 1. [License](#license) @@ -107,11 +108,6 @@ class: 'alien' }; - // bad - var superman = { - klass: 'alien' - }; - // good var superman = { type: 'alien' @@ -263,7 +259,7 @@ ```javascript // anonymous function expression - var anonymous = function() { + var anonymous = function () { return true; }; @@ -273,7 +269,7 @@ }; // immediately-invoked function expression (IIFE) - (function() { + (function () { console.log('Welcome to the Internet. Please follow me.'); })(); ``` @@ -403,7 +399,7 @@ ```javascript // bad - function() { + function () { test(); console.log('doing stuff..'); @@ -419,7 +415,7 @@ } // good - function() { + function () { var name = getName(); test(); @@ -435,7 +431,7 @@ } // bad - function() { + function () { var name = getName(); if (!arguments.length) { @@ -446,7 +442,7 @@ } // good - function() { + function () { if (!arguments.length) { return false; } @@ -457,6 +453,88 @@ } ``` + - Only use single line for each variable. + + ```javascript + // bad + function () { + var count = 0, + options = { + name: 'Luke', + race: 'Jedi' + }, + i; + + // ... + } + + // good + function () { + var count = 0, + options, + i; + + options = { + name: 'Luke', + race: 'Jedi' + }; + + // ... + } + ``` + + - For readability, only use single comment line for a variable if + necessary. Prefer documenting variable in the function documentation. + + ```javascript + // bad + function () { + var count, + + /* Hash that contain character options. + * + * @typedef options + * @property {String} name - Name of the character. + * @property {String} race - Race of the character. + */ + options, + + // index for the for loop. + i; + + // ... + } + + // better + function () { + var count, + // character's options hash + options, + i; + } + + // good + function () { + var count, + characterOptions, + i; + + // ... + } + + // good + /** + * ... + * + * Internally, character's options are stored in the `options` hash. + */ + function () { + var count, + options, + i; + } + ``` + **[⬆ back to top](#table-of-contents)** @@ -498,7 +576,7 @@ anonymous(); // => TypeError anonymous is not a function - var anonymous = function() { + var anonymous = function () { console.log('anonymous function expression'); }; } @@ -616,20 +694,86 @@ } // bad - function() { return false; } + function () { return false; } // good - function() { + function () { return false; } ``` + - Put `else` and `catch` on the same line with closing brace. + + ```javascript + // bad + if (test) { + return true; + } + else { + return false; + } + + // good + if (test) { + return 1; + } else if (anotherTest) { + return 0; + } else { + return -1; + } + + // bad + try { + dragonsBeHere(); + } + catch (e) { + log.error('very bad: ' + e); + } + + // good + try { + dragonsBeHere(); + } catch (e) { + log.error('very bad: ' + e); + } + ``` + **[⬆ back to top](#table-of-contents)** ## Comments - - Use `/** ... */` for multiline comments. Include a description, specify types and values for all parameters and return values. + - Use following format for function/class comments: + + ```javascript + /** + * + */ + ``` + + - Other (random) multiline comments should take either of + the following two forms: + + ```javascript + /* + * + */ + + // + // + // + ``` + + - In function comments, include description, specify + types and values for necessary parameters and return values. + - First sentence of the function docstring should fit on one line and + should prescribe the function's effect as a command ("Do this", + "Return that"), not as a description; e.g. don't write "Returns the + pathname ..." + - Use [JSDoc](http://usejsdoc.org) + - You don't always have to document every parameter and return value if + they are obvious. + - Use `@typedef` to specify complex structures. ```javascript // bad @@ -647,11 +791,13 @@ // good /** - * make() returns a new element - * based on the passed in tag name + * Return a new element. + * + * This is a longer description of the function and will + * give all the necessary details. * - * @param tag - * @return element + * @param {String} tag - descriptive tag + * @returns {Element} element */ function make(tag) { @@ -664,12 +810,6 @@ - Use `//` for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment. ```javascript - // bad - var active = true; // is current tab - - // good - // is current tab - var active = true; // bad function getType() { @@ -691,53 +831,66 @@ } ``` - - Prefixing your comments with `FIXME` or `TODO` helps other developers quickly understand if you're pointing out a problem that needs to be revisited, or if you're suggesting a solution to the problem that needs to be implemented. These are different than regular comments because they are actionable. The actions are `FIXME -- need to figure this out` or `TODO -- need to implement`. - - - Use `// FIXME:` to annotate problems + - Use `@todo` to annotate todos ```javascript function Calculator() { - // FIXME: shouldn't use a global here - total = 0; + // @todo total should be configurable by an options param + this.total = 0; return this; } - ``` + ``` + +**[⬆ back to top](#table-of-contents)** + - - Use `// TODO:` to annotate solutions to problems +## Line Length + +All JSHint rules should be set to max 100 char line length. There are some +cases where it is actually better for readability if this rule is broken. A +good example would be a REST API definition on the server side. In those +cases, override the JSHint rule for that code block. For example: ```javascript - function Calculator() { + /*jshint maxlen:200*/ - // TODO: total should be configurable by an options param - this.total = 0; + ... + { + name: 'releaseServiceWindow', + version: 1, + urls: [ + { path: '/schedule/{sid}/service-window/', name: 'schedule/serviceWindow' }, + { path: '/schedule/{sid}/service-window/{swid}', name: 'schedule/serviceWindow/*'} + ] + }, + ... - return this; - } - ``` + /*jshint maxlen:100*/ + ``` **[⬆ back to top](#table-of-contents)** ## Whitespace - - Use soft tabs set to 2 spaces + - Use soft tabs set to 4 spaces ```javascript // bad - function() { - ∙∙∙∙var name; + function () { + var name; } // bad - function() { + function () { ∙var name; } // good - function() { - ∙∙var name; + function () { + ∙∙∙∙var name; } ``` @@ -746,27 +899,41 @@ ```javascript // bad function test(){ - console.log('test'); + console.log('test'); } // good function test() { - console.log('test'); + console.log('test'); } // bad dog.set('attr',{ - age: '1 year', - breed: 'Bernese Mountain Dog' + age: '1 year', + breed: 'Bernese Mountain Dog' }); // good dog.set('attr', { - age: '1 year', - breed: 'Bernese Mountain Dog' + age: '1 year', + breed: 'Bernese Mountain Dog' }); ``` + - Place 1 space after keywords (such as `if`, `for` and `function`). + + ```javascript + // bad + var foo = function() { + alert('hello'); + }; + + // good + var foo = function () { + alert('hello'); + }; + ``` + - Set off operators with spaces. ```javascript @@ -781,23 +948,23 @@ ```javascript // bad - (function(global) { - // ...stuff... + (function (global) { + // ...stuff... })(this); ``` ```javascript // bad - (function(global) { - // ...stuff... + (function (global) { + // ...stuff... })(this);↵ ↵ ``` ```javascript // good - (function(global) { - // ...stuff... + (function (global) { + // ...stuff... })(this);↵ ``` @@ -809,11 +976,11 @@ // good $('#items') - .find('.selected') - .highlight() - .end() - .find('.open') - .updateCount(); + .find('.selected') + .highlight() + .end() + .find('.open') + .updateCount(); // bad var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').class('led', true) @@ -823,13 +990,13 @@ // 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); + .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); ``` **[⬆ back to top](#table-of-contents)** @@ -866,9 +1033,17 @@ }; ``` - - Additional trailing comma: **Nope.** This can cause problems with IE6/7 and IE9 if it's in quirksmode. Also, in some implementations of ES3 would add length to an array if it had an additional trailing comma. This was clarified in ES5 ([source](http://es5.github.io/#D)): + - Additional trailing comma: **Nope.** This can cause problems with IE6/7 and + IE9 if it's in quirksmode. Also, in some implementations of ES3 would add + length to an array if it had an additional trailing comma. This was + clarified in ES5 ([source](http://es5.github.io/#D)): - > Edition 5 clarifies the fact that a trailing comma at the end of an ArrayInitialiser does not add to the length of the array. This is not a semantic change from Edition 3 but some implementations may have previously misinterpreted this. + > Edition 5 clarifies the fact that a trailing comma at the end of an + ArrayInitialiser does not add to the length of the array. This is not a + semantic change from Edition 3 but some implementations may have previously + misinterpreted this. + + It is fine to use the extra comma in node.js environment. ```javascript // bad @@ -903,19 +1078,19 @@ ```javascript // bad - (function() { + (function () { var name = 'Skywalker' return name })() // good - (function() { + (function () { var name = 'Skywalker'; return name; })(); // good - ;(function() { + ;(function () { var name = 'Skywalker'; return name; })(); @@ -924,6 +1099,26 @@ **[⬆ back to top](#table-of-contents)** +## File Headers + +All JavaScript files should have the following file header and footer: + + ```javascript + // -*- coding: utf-8 -*- + // filename.js + // created: 2014-05-22 08:10:10 + // + + + + // + // filename.js ends here + ``` + + +**[⬆ back to top](#table-of-contents)** + + ## Type Casting & Coercion - Perform type coercion at the beginning of the statement. @@ -1036,7 +1231,7 @@ // good var thisIsMyObject = {}; - function thisIsMyFunction() {} + function thisIsMyfunction () {} var user = new User({ name: 'Bob Parr' }); @@ -1064,7 +1259,9 @@ }); ``` - - Use a leading underscore `_` when naming private properties + - Use a leading underscore `_` when naming private properties (note that + this is not necessary when using the module pattern or when protecting + ES5 style variables with `Object.create()`) ```javascript // bad @@ -1075,46 +1272,50 @@ this._firstName = 'Panda'; ``` - - When saving a reference to `this` use `_this`. + - When saving a reference to `this` use `me`. ```javascript // bad - function() { + function () { var self = this; - return function() { + return function () { console.log(self); }; } // bad - function() { - var that = this; - return function() { - console.log(that); + function () { + var _this = this; + return function () { + console.log(_this); }; } // good - function() { - var _this = this; - return function() { - console.log(_this); + function () { + var me = this; + return function () { + console.log(me); }; } ``` - - Name your functions. This is helpful for stack traces. + - When implementing the JavaScript module pattern use `that` for referencing + the "public" interface. ```javascript - // bad - var log = function(msg) { - console.log(msg); - }; - // good - var log = function log(msg) { - console.log(msg); - }; + var mymodule = (function mymodule() { + var that = {}, + privateCounter = 0; + + that.increment = function increment() { + privateCounter += 1; + return privateCounter; + }; + + return that; + })(); ``` - **Note:** IE8 and below exhibit some quirks with named function expressions. See [http://kangax.github.io/nfe/](http://kangax.github.io/nfe/) for more info. @@ -1124,54 +1325,7 @@ ## Accessors - - Accessor functions for properties are not required - - If you do make accessor functions use getVal() and setVal('hello') - - ```javascript - // bad - dragon.age(); - - // good - dragon.getAge(); - - // bad - dragon.age(25); - - // good - dragon.setAge(25); - ``` - - - If the property is a boolean, use isVal() or hasVal() - - ```javascript - // bad - if (!dragon.age()) { - return false; - } - - // good - if (!dragon.hasAge()) { - return false; - } - ``` - - - It's okay to create get() and set() functions, but be consistent. - - ```javascript - 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]; - }; - ``` + - Accessor functions (simle getters and setters) for properties are not required **[⬆ back to top](#table-of-contents)** @@ -1206,16 +1360,16 @@ }; ``` - - Methods can return `this` to help with method chaining. + - Methods should return `this` to help with method chaining. ```javascript // bad - Jedi.prototype.jump = function() { + Jedi.prototype.jump = function () { this.jumping = true; return true; }; - Jedi.prototype.setHeight = function(height) { + Jedi.prototype.setHeight = function (height) { this.height = height; }; @@ -1224,12 +1378,12 @@ luke.setHeight(20) // => undefined // good - Jedi.prototype.jump = function() { + Jedi.prototype.jump = function () { this.jumping = true; return this; }; - Jedi.prototype.setHeight = function(height) { + Jedi.prototype.setHeight = function (height) { this.height = height; return this; }; @@ -1261,6 +1415,34 @@ **[⬆ back to top](#table-of-contents)** +## Functional Style + + - Avoid generic for-loops (i.e. `for (i = 0; i < array.length; i += 1)`) + because + - They make code difficult to read + - They may have all kinds of side effects + - Prefer using functional programming constructs such as + - `map`: to convert an array to another array + - `reduce`: to reduce an array of items into a single value + - `filter`: to filter out unwanted elements + - Familiarize yourself with all different functions available in the + [lodash](http://lodash.com/docs) library. When it comes to dealing with + collections, most of the time it already has what you would otherwise + have to implement yourself. + - Using these functions makes it easy for the reader to quickly see + what the code is supposed to be doing and the reader can trust that the + code inside the loop doesn't (must not) have any side effects. + - `forEach` is the only exception where we allow the code inside the loop to + have side effects since it has been used for drop-in replacement for more + traditional `for (i = 0; i < array.length; i += 1)` construct. + - Sometimes though, for performance reasons for large collections, you may + want to consider replacing subsequent functional constructs with single + for-loop where you do many things at once. This should be vary rare however. + + +**[⬆ back to top](#table-of-contents)** + + ## Events - When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass a hash instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event. For example, instead of: @@ -1271,7 +1453,7 @@ ... - $(this).on('listingUpdated', function(e, listingId) { + $(this).on('listingUpdated', function (e, listingId) { // do something with listingId }); ``` @@ -1284,7 +1466,7 @@ ... - $(this).on('listingUpdated', function(e, data) { + $(this).on('listingUpdated', function (e, data) { // do something with data.listingId }); ``` @@ -1294,31 +1476,11 @@ ## Modules - - The module should start with a `!`. This ensures that if a malformed module forgets to include a final semicolon there aren't errors in production when the scripts get concatenated. [Explanation](https://github.com/airbnb/javascript/issues/44#issuecomment-13063933) - - The file should be named with camelCase, live in a folder with the same name, and match the name of the single export. - - Add a method called `noConflict()` that sets the exported module to the previous version and returns this one. + - In Node.js files should be named with camelCase. + - In Ember projects files should be named with hyphens (e.g. `route-header.js`). + - All files should use `utf-8` encoding. - Always declare `'use strict';` at the top of the module. - ```javascript - // fancyInput/fancyInput.js - - !function(global) { - 'use strict'; - - var previousFancyInput = global.FancyInput; - - function FancyInput(options) { - this.options = options || {}; - } - - FancyInput.noConflict = function noConflict() { - global.FancyInput = previousFancyInput; - return FancyInput; - }; - - global.FancyInput = FancyInput; - }(this); - ``` **[⬆ back to top](#table-of-contents)** @@ -1394,27 +1556,42 @@ ## Testing - - **Yup.** +### Testing private functions in Node.js + + - Gather all private functions in private variable called `internals` + - Expose the `internals` variable when running tests ```javascript - function() { - return true; + var internals = {}; + + internals.myPrivateFunction = function () { + // do private stuff + }; + + // in the end of the module expose internals for tests + if (process.env.NODE_ENV === 'test') { + exports.internals = internals; } ``` + **[⬆ back to top](#table-of-contents)** -## Performance +## Parting Words - - [On Layout & Web Performance](http://kellegous.com/j/2013/01/26/layout-performance/) - - [String vs Array Concat](http://jsperf.com/string-vs-array-concat/2) - - [Try/Catch Cost In a Loop](http://jsperf.com/try-catch-in-loop-cost) - - [Bang Function](http://jsperf.com/bang-function) - - [jQuery Find vs Context, Selector](http://jsperf.com/jquery-find-vs-context-sel/13) - - [innerHTML vs textContent for script text](http://jsperf.com/innerhtml-vs-textcontent-for-script-text) - - [Long String Concatenation](http://jsperf.com/ya-string-concat) - - Loading... +Remeber that: + +*"Code is read many more times than it is written"* + +So, it's all about readability. If any of the conventions laid out on this page +contradicts with readability in a given scenario, feel free to break the +convention to make way for better readability. + +Bear in mind though that linting errors are considered real errors and all +projects must pass with zero linting errors. Therefore, if you have to break +one of the JSHint rules, override the rule for that file or for that code +block instead of changing the global project rules. **[⬆ back to top](#table-of-contents)** @@ -1426,6 +1603,16 @@ - [Annotated ECMAScript 5.1](http://es5.github.com/) +**Performance** + + - [On Layout & Web Performance](http://kellegous.com/j/2013/01/26/layout-performance/) + - [String vs Array Concat](http://jsperf.com/string-vs-array-concat/2) + - [Try/Catch Cost In a Loop](http://jsperf.com/try-catch-in-loop-cost) + - [Bang Function](http://jsperf.com/bang-function) + - [jQuery Find vs Context, Selector](http://jsperf.com/jquery-find-vs-context-sel/13) + - [innerHTML vs textContent for script text](http://jsperf.com/innerhtml-vs-textcontent-for-script-text) + - [Long String Concatenation](http://jsperf.com/ya-string-concat) + **Other Styleguides** - [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) @@ -1477,54 +1664,6 @@ **[⬆ back to top](#table-of-contents)** -## In the Wild - - This is a list of organizations that are using this style guide. Send us a pull request or open an issue and we'll add you to the list. - - - **Aan Zee**: [AanZee/javascript](https://github.com/AanZee/javascript) - - **Airbnb**: [airbnb/javascript](https://github.com/airbnb/javascript) - - **American Insitutes for Research**: [AIRAST/javascript](https://github.com/AIRAST/javascript) - - **Compass Learning**: [compasslearning/javascript-style-guide](https://github.com/compasslearning/javascript-style-guide) - - **DailyMotion**: [dailymotion/javascript](https://github.com/dailymotion/javascript) - - **Digitpaint** [digitpaint/javascript](https://github.com/digitpaint/javascript) - - **ExactTarget**: [ExactTarget/javascript](https://github.com/ExactTarget/javascript) - - **Gawker Media**: [gawkermedia/javascript](https://github.com/gawkermedia/javascript) - - **GeneralElectric**: [GeneralElectric/javascript](https://github.com/GeneralElectric/javascript) - - **GoodData**: [gooddata/gdc-js-style](https://github.com/gooddata/gdc-js-style) - - **Grooveshark**: [grooveshark/javascript](https://github.com/grooveshark/javascript) - - **How About We**: [howaboutwe/javascript](https://github.com/howaboutwe/javascript) - - **Mighty Spring**: [mightyspring/javascript](https://github.com/mightyspring/javascript) - - **MinnPost**: [MinnPost/javascript](https://github.com/MinnPost/javascript) - - **ModCloth**: [modcloth/javascript](https://github.com/modcloth/javascript) - - **Money Advice Service**: [moneyadviceservice/javascript](https://github.com/moneyadviceservice/javascript) - - **National Geographic**: [natgeo/javascript](https://github.com/natgeo/javascript) - - **National Park Service**: [nationalparkservice/javascript](https://github.com/nationalparkservice/javascript) - - **Orion Health**: [orionhealth/javascript](https://github.com/orionhealth/javascript) - - **Peerby**: [Peerby/javascript](https://github.com/Peerby/javascript) - - **Razorfish**: [razorfish/javascript-style-guide](https://github.com/razorfish/javascript-style-guide) - - **SeekingAlpha**: [seekingalpha/javascript-style-guide](https://github.com/seekingalpha/javascript-style-guide) - - **reddit**: [reddit/styleguide/javascript](https://github.com/reddit/styleguide/tree/master/javascript) - - **REI**: [reidev/js-style-guide](https://github.com/reidev/js-style-guide) - - **Ripple**: [ripple/javascript-style-guide](https://github.com/ripple/javascript-style-guide) - - **Shutterfly**: [shutterfly/javascript](https://github.com/shutterfly/javascript) - - **Userify**: [userify/javascript](https://github.com/userify/javascript) - - **Zillow**: [zillow/javascript](https://github.com/zillow/javascript) - - **ZocDoc**: [ZocDoc/javascript](https://github.com/ZocDoc/javascript) - -## Translation - - This style guide is also available in other languages: - - - :de: **German**: [timofurrer/javascript-style-guide](https://github.com/timofurrer/javascript-style-guide) - - :jp: **Japanese**: [mitsuruog/javacript-style-guide](https://github.com/mitsuruog/javacript-style-guide) - - :br: **Portuguese**: [armoucar/javascript-style-guide](https://github.com/armoucar/javascript-style-guide) - - :cn: **Chinese**: [adamlu/javascript-style-guide](https://github.com/adamlu/javascript-style-guide) - - :es: **Spanish**: [paolocarrasco/javascript-style-guide](https://github.com/paolocarrasco/javascript-style-guide) - - :kr: **Korean**: [tipjs/javascript-style-guide](https://github.com/tipjs/javascript-style-guide) - - :fr: **French**: [nmussy/javascript-style-guide](https://github.com/nmussy/javascript-style-guide) - - :ru: **Russian**: [uprock/javascript](https://github.com/uprock/javascript) - - :bg: **Bulgarian**: [borislavvv/javascript](https://github.com/borislavvv/javascript) - ## The JavaScript Style Guide Guide - [Reference](https://github.com/airbnb/javascript/wiki/The-JavaScript-Style-Guide-Guide)