Node.js 风格指南
Table of Contents generated with DocToc
- 参考资料
- 类型
- 对象
- 数组
- 字符串
- 函数
- 属性
- 变量
- Requires
- 回调函数
- Try catch
- 提升
- 条件表达式 & 相等性
- 代码块
- 注释
- 空格
- 逗号
- 分号作为语句块的结束
- 类型转换 & 强制类型转换
- 命名约定
- 访问器
- 构造函数
- ES6箭头函数
- ES6增强的对象字面量
- ES6模板字符串
- ES6函数参数增强
- ES6新增关键字let和const
- ES6迭代器和
for..of
- ES6生成器
- ES6模块
- ES6新增集合Map和Set
- ES6 Promise
- 推荐的书
- 推荐的博客
参考资料
- Airbnb styleguide
- RisingStack’s Node.js styleguide
- Caolan’s Node.js styleguide
- Felixge’s Node.js styleguide
- Baidu EFE: ES6 develop overview
类型
原始类型: 当你访问一个原始数据类型时,你直接操作的是它的值
string
number
boolean
null
undefined
123456var foo = 1;var bar = foo;bar = 9;console.log(foo, bar); // => 1, 9复杂情况: 当你访问的是一个复杂类型时,你操作的是它的引用
object
array
function
123456var foo = [1, 2];var bar = foo;bar[0] = 9;console.log(foo[0], bar[0]); // => 9, 9
对象
在对象创建时使用字面量语法
12345// badvar item = new Object();// goodvar item = {};使用可读的别名,避免使用保留字
1234567891011121314// badvar superman = {class: 'alien'};// badvar superman = {klass: 'alien'};// goodvar superman = {type: 'alien'};
数组
使用字面量语法创建数组
12345// badvar items = new Array();// goodvar items = [];当你不知道数组的长度时使用Array#push.
12345678var someStack = [];// badsomeStack[someStack.length] = 'abracadabra';// goodsomeStack.push('abracadabra');当你需要复制数据时使用Array#slice. jsPerf
1234567891011var len = items.length;var itemsCopy = [];var i;// badfor (i = 0; i < len; i++) {itemsCopy[i] = items[i];}// gooditemsCopy = items.slice();将一个类数组对象转为数组,使用Array#slice.
1234function trigger() {var args = Array.prototype.slice.call(arguments);...}
字符串
使用单引号
''
表示字符串1234567891011// badvar name = "Bob Parr";// goodvar name = 'Bob Parr';// badvar fullName = "Bob " + this.lastName;// goodvar fullName = 'Bob ' + this.lastName;长于80个字符的字符串应该被写成多行(使用字符串拼接或ES6的模板字符串)
注意:如果过度使用,长字符串拼接会影响性能. jsPerf & Discussion
123456789101112131415161718192021// badvar 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.';// badvar 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.';// goodvar 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.';// goodvar errorMessage = `helloworldthisis`;当使用程序来生成字符串时,使用Array#join,而不是字符串拼接.
123456789101112131415161718192021222324252627282930313233343536373839var items;var messages;var length;var i;messages = [{state: 'success',message: 'This one worked.'}, {state: 'success',message: 'This one worked as well.'}, {state: 'error',message: 'This one did not work.'}];length = messages.length;// badfunction inbox(messages) {items = '<ul>';for (i = 0; i < length; i++) {items += '<li>' + messages[i].message + '</li>';}return items + '</ul>';}// goodfunction inbox(messages) {items = [];for (i = 0; i < length; i++) {items[i] = messages[i].message;}return '<ul><li>' + items.join('</li><li>') + '</li></ul>';}
函数
函数表达式:
1234567891011121314// anonymous function expressionvar anonymous = function() {return true;};// named function expressionvar named = function named() {return true;};// immediately-invoked function expression (IIFE)(function() {console.log('Welcome to the Internet. Please follow me.');})();永远不要再一个非函数语句块内使用函数声明(if, while, 等). 将函数赋值给一个变量.
1234567891011121314// badif (currentUser) {function test() {console.log('Nope.');}}// goodvar test;if (currentUser) {test = function test() {console.log('Yup.');};}永远不要将你的参数命名为
arguments
, 这会与函数范围内的arguments
对象冲突.123456789// badfunction nope(name, options, arguments) {// ...stuff...}// goodfunction yup(name, options, args) {// ...stuff...}
属性
当访问属性时请使用
.
操作符.12345678910var luke = {jedi: true,age: 28};// badvar isJedi = luke['jedi'];// goodvar isJedi = luke.jedi;当你要用变量访问属性时,请使用
[]
.12345678910var luke = {jedi: true,age: 28};function getProp(prop) {return luke[prop];}var isJedi = getProp('jedi');
变量
始终使用
var
来声明变量. 否则会创建全局变量,污染全局命名空间.12345// badsuperPower = new SuperPower();// goodvar superPower = new SuperPower();声明变量时使用一个新行, 并且每一行都使用
var
来声明.123456789// badvar items = getItems(),goSportsTeam = true,dragonball = 'z';// goodvar items = getItems();var goSportsTeam = true;var dragonball = 'z';最后声明未赋值的便利. 后面如果你要使用前面的变量进行赋值时会显得很便利.
12345678910111213// badvar i;var items = getItems();var dragonball;var goSportsTeam = true;var len;// goodvar items = getItems();var goSportsTeam = true;var dragonball;var length;var i;避免冗余的变量声明, 可已使用
Object
对象来构建命名空间.123456789101112// badvar kaleidoscopeName = '..';var kaleidoscopeLens = [];var kaleidoscopeColors = [];// goodvar kaleidoscope = {name: '..',lens: [],colors: []};在变量作用范围的最顶端声明变量. 这可以帮你避免赋值提升的问题.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253// badfunction() {test();console.log('doing stuff..');//..other stuff..var name = getName();if (name === 'test') {return false;}return name;}// goodfunction() {var name = getName();test();console.log('doing stuff..');//..other stuff..if (name === 'test') {return false;}return name;}// badfunction() {var name = getName();if (!arguments.length) {return false;}return true;}// goodfunction() {if (!arguments.length) {return false;}var name = getName();return true;}
Requires
使用如下顺序来组织node代码中的require语句:
- core modules
- npm modules
- others
12345678910111213// badvar Car = require('./models/Car');var async = require('async');var http = require('http');// goodvar http = require('http');var fs = require('fs');var async = require('async');var mongoose = require('mongoose');var Car = require('./models/Car');当你引入模块时,不要加
.js
后缀12345// badvar Batmobil = require('./models/Car.js');// goodvar Batmobil = require('./models/Car');
回调函数
在回调函数中要始终检测错误
12345678910111213//baddatabase.get('pokemons', function(err, pokemons) {console.log(pokemons);});//gooddatabase.get('drabonballs', function(err, drabonballs) {if (err) {// handle the error somehow, maybe return with a callbackreturn console.log(err);}console.log(drabonballs);});遇到错误时从回调中返回
123456789101112131415161718//baddatabase.get('drabonballs', function(err, drabonballs) {if (err) {// if not return hereconsole.log(err);}// this line will be executed as wellconsole.log(drabonballs);});//gooddatabase.get('drabonballs', function(err, drabonballs) {if (err) {// handle the error somehow, maybe return with a callbackreturn console.log(err);}console.log(drabonballs);});当你要开发接口给外部时,在你的回调函数中使用描述性的参数。它能够让你的代码更可读。
123456789101112131415161718// badfunction getAnimals(done) {Animal.get(done);}// goodfunction getAnimals(done) {Animal.get(function(err, animals) {if(err) {return done(err);}return done(null, {dogs: animals.dogs,cats: animals.cats})});}
Try catch
只能在同步函数中使用
throw
Try-catch 语句块不能被用在异步代码块中。
123456789101112131415161718//badfunction readPackageJson (callback) {fs.readFile('package.json', function(err, file) {if (err) {throw err;}...});}//goodfunction readPackageJson (callback) {fs.readFile('package.json', function(err, file) {if (err) {return callback(err);}...});}在同步调用中捕获错误,
JSON.parse()
应该使用try-catch
语句块1234567891011//badvar data = JSON.parse(jsonAsAString);//goodvar data;try {data = JSON.parse(jsonAsAString);} catch (e) {//handle error - hopefully not with a console.log ;)console.log(e);}
提升
变量声明会被提升到作用域的顶端,而赋值操作则不会。
12345678910111213141516171819// 先看个简单的例子,显然它会抛出错误function example() {console.log(notDefined); // => throws a ReferenceError}// 我们先使用了一个变量,而后再声明并初始化这个变量// 输出结果没有报错,而是 `undefined`,意思是未被初始化function example() {console.log(declaredButNotAssigned); // => undefinedvar declaredButNotAssigned = true;}// 变量声明部分会被提升,赋值部分仍保持不变// 上面的代码等同于function example() {var declaredButNotAssigned;console.log(declaredButNotAssigned); // => undefineddeclaredButNotAssigned = true;}匿名函数表达式会提升它们的变量名,但是函数赋值部门不会被提升
123456789function example() {console.log(anonymous); // => undefinedanonymous(); // => TypeError anonymous is not a functionvar anonymous = function() {console.log('anonymous function expression');};}命名函数表达式会提升它们的变量名,但函数名或函数体不会被提升。
1234567891011121314151617181920212223function example() {console.log(named); // => undefinednamed(); // => TypeError named is not a functionsuperPower(); // => ReferenceError superPower is not definedvar named = function superPower() {console.log('Flying');};}// the same is true when the function name// is the same as the variable name.function example() {console.log(named); // => undefinednamed(); // => TypeError named is not a functionvar named = function named() {console.log('named');}}函数声明会被整体提升到作用域顶端
1234567function example() {superPower(); // => Flyingfunction superPower() {console.log('Flying');}}更多信息请参考 JavaScript Scoping & Hoisting by Ben Cherry
条件表达式 & 相等性
- 使用
===
和!==
来代替==
和!=
. 条件表达式会被使用
ToBoolean
方法进行强制类型转换。并且服从如下规则:- Objects 被转换为 true
- Undefined 被转换为 false
- Null 被转换为 false
- Booleans 被转换为 实际的boolean值
- Numbers 被转换为 false 如果是 +0, -0, or NaN, 其他都为 true
- Strings 被转换为 false 如果是空字符串
''
, 其他都为 true
1234if ([0]) {// true// 数组是对象,对象始终被转换为 `true`}使用缩减版.
12345678910111213141516171819// badif (name !== '') {// ...stuff...}// goodif (name) {// ...stuff...}// badif (collection.length > 0) {// ...stuff...}// goodif (collection.length) {// ...stuff...}更多信息请参考 Truth Equality and JavaScript by Angus Croll
代码块
所有的多行代码块都要使用大括号,并且不要写在一行
12345678910111213141516171819// badif (test)return false;// badif (test) return false;// goodif (test) {return false;}// badfunction() { return false; }// goodfunction() {return false;}
注释
使用
/** ... */
进行多行注释. 请在你们加入注释说明,指明参数和返回值的类型123456789101112131415161718192021222324252627// bad// make() returns a new element// based on the passed in tag name//// @param <String> tag// @return <Element> elementfunction 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;}使用
//
进行单行注释. 请用一个新行来添加注释。并在注释行前增加一个空行。12345678910111213141516171819202122232425// badvar active = true; // is current tab// good// is current tabvar active = true;// badfunction getType() {console.log('fetching type...');// set the default type to 'no type'var type = this._type || 'no type';return type;}// goodfunction getType() {console.log('fetching type...');// set the default type to 'no type'var type = this._type || 'no type';return type;}如果是为了指明一个错误,请在你的注释前加上
FIXME
或TODO
前缀来帮助其他开发者快速的了解你的注释意图。
其中FIXME
可以表示这个问题需要解决,或者TODO
来表示需要被实现的功能块。使用
// FIXME:
来注解一个问题。1234567function Calculator() {// FIXME: shouldn't use a global heretotal = 0;return this;}使用
// TODO:
来注解一个需要被实现(完成)的任务。1234567function Calculator() {// TODO: total should be configurable by an options paramthis.total = 0;return this;}
空格
推荐使用2个空格作为缩进
1234567891011121314// badfunction() {∙∙∙∙var name;}// badfunction() {∙var name;}// goodfunction() {∙∙var name;}在所有起始的大括号前加一个空格
123456789101112131415161718192021// badfunction test(){console.log('test');}// goodfunction test() {console.log('test');}// baddog.set('attr',{age: '1 year',breed: 'Bernese Mountain Dog'});// gooddog.set('attr', {age: '1 year',breed: 'Bernese Mountain Dog'});在操作符见使用一个空格
12345// badvar x=y+5;// goodvar x = y + 5;文件结束后增加一个空行
1234// bad(function(global) {// ...stuff...})(this);12345// bad(function(global) {// ...stuff...})(this);↵↵1234// good(function(global) {// ...stuff...})(this);↵对链接起来的方法使用缩进成多行的形式
1234567891011121314151617181920212223242526// bad$('#items').find('.selected').highlight().end().find('.open').updateCount();// good$('#items').find('.selected').highlight().end().find('.open').updateCount();// badvar 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);// goodvar 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);
逗号
推荐的做法是逗号在每一行的末尾
123456789101112131415// badvar hero = {firstName: 'Bob', lastName: 'Parr', heroName: 'Mr. Incredible', superPower: 'strength'};// goodvar hero = {firstName: 'Bob',lastName: 'Parr',heroName: 'Mr. Incredible',superPower: 'strength'};
分号作为语句块的结束
Yup.
1234567891011121314151617// bad(function() {var name = 'Skywalker'return name})()// good(function() {var name = 'Skywalker';return name;})();// good;(function() {var name = 'Skywalker';return name;})();
类型转换 & 强制类型转换
- 在声明语句的最前端执行强制类型转换.
Strings:
12345678910111213// => this.reviewScore = 9;// badvar totalScore = this.reviewScore + '';// goodvar totalScore = '' + this.reviewScore;// badvar totalScore = '' + this.reviewScore + ' total score';// goodvar totalScore = this.reviewScore + ' total score';使用
parseInt
来进行整数的类型转换,并且始终提供一个基数.12345678910111213141516171819var inputValue = '4';// badvar val = new Number(inputValue);// badvar val = +inputValue;// badvar val = inputValue >> 0;// badvar val = parseInt(inputValue);// goodvar val = Number(inputValue);// goodvar val = parseInt(inputValue, 10);如果某种情况下你为了性能原因需要避免使用
parseInt
,而是使用移位操作符,请添加注释说明1234567// 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;Note: 在使用移位操作符时需要特别谨慎. 数字使用 64-bit values表示的, 但是移位操作符总是返回32-bit的整数 (source). 这对大于32-bit的整数而言,这会导致意向不到的行为. Discussion. 最大的有符号32位整数是 2,147,483,647:
1232147483647 >> 0 //=> 21474836472147483648 >> 0 //=> -21474836482147483649 >> 0 //=> -2147483647Booleans:
12345678910var age = 0;// badvar hasAge = new Boolean(age);// goodvar hasAge = Boolean(age);// goodvar hasAge = !!age;
命名约定
避免使用当个字符命名,使用描述性的名字:
123456789// badfunction q() {// ...stuff...}// goodfunction query() {// ..stuff..}对于对象、函数、和实例采用小驼峰(camelCase)命名法
1234567891011121314// badvar OBJEcttsssss = {};var this_is_my_object = {};function c() {}var u = new user({name: 'Bob Parr'});// goodvar thisIsMyObject = {};function thisIsMyFunction() {}var user = new User({name: 'Bob Parr'});当命名类或构造函数时使用大驼峰或Pascal命名法(PascalCase)
1234567891011121314151617// badfunction user(options) {this.name = options.name;}var bad = new user({name: 'nope'});// goodfunction User(options) {this.name = options.name;}var good = new User({name: 'yup'});在私有属性前加上一个
_
前缀123456// badthis.__firstName__ = 'Panda';this.firstName_ = 'Panda';// goodthis._firstName = 'Panda';当你要保存
this
值时,可以将其命名为_this
.1234567891011121314151617181920212223// badfunction() {var self = this;return function() {console.log(self);};}// badfunction() {var that = this;return function() {console.log(that);};}// goodfunction() {var _this = this;return function() {console.log(_this);};}命名你的函数。这将有助于堆栈跟踪。
123456789// badvar log = function(msg) {console.log(msg);};// goodvar log = function log(msg) {console.log(msg);};
访问器
- 属性访问器并不是必须的。
如果你确实需要,请命名为 getVal() 和 setVal(‘hello’) 的形式
1234567891011// baddragon.age();// gooddragon.getAge();// baddragon.age(25);// gooddragon.setAge(25);如果属性是一个布尔值,请使用 isVal() 或 hasVal()
123456789// badif (!dragon.age()) {return false;}// goodif (!dragon.hasAge()) {return false;}你也可以创建 get() 和 set() 函数, 但一定要保持一致.
12345678910111213function 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];};
构造函数
在原型链上增加属性,而不是覆写原型链。
1234567891011121314151617181920212223function Jedi() {console.log('new jedi');}// badJedi.prototype = {fight: function fight() {console.log('fighting');},block: function block() {console.log('blocking');}};// goodJedi.prototype.fight = function fight() {console.log('fighting');};Jedi.prototype.block = function block() {console.log('blocking');};你可以在方法中返回
this
从而来构建可链接的方法。1234567891011121314151617181920212223242526272829// badJedi.prototype.jump = function() {this.jumping = true;return true;};Jedi.prototype.setHeight = function(height) {this.height = height;};var luke = new Jedi();luke.jump(); // => trueluke.setHeight(20) // => undefined// goodJedi.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);你可以创建一个自定义的
toString()
方法,但是你要确保它能正常工作并且没有其他副作用。123456789101112function 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();};
ES6箭头函数
当你必须使用函数表达式(或传递一个匿名函数时),使用箭头函数符号(能够自动绑定
this
到父对象)1234567// bad[1, 2, 3].map(function (x) {return x * x;});// good[1, 2, 3].map((x) => x * x);
建议所有的Arrow Function的参数均使用
()
包裹,即便只有一个参数:123456789101112131415161718192021222324252627282930// goodlet foo = (x) => x + 1;// badlet foo = x => x + 1;``- 对于对象、类中的方法,使用增强的对象字面量```javascript// goodlet foo = {bar () {// code}}// badlet foo = {bar: () => {// code}}// badlet foo = {bar: function () {// code}}
ES6增强的对象字面量
可以在对象总直接定义方法
123456// goodlet foo = {bar() {// code}}
可使用通过计算得出的键值
12345// 当你需要的时候使用let MY_KEY = 'bar';let foo = {[MY_KEY + 'Hash']: 123}
与当前scope中同名变量的简写
12345// badlet bar = 'bar';let foo = {bar // 相当于bar: bar};
ES6模板字符串
不推荐使用多行字符串,因为不方便代码缩进
12345// badlet html =`<div><p>Hello world</p></div>`
推荐使用ES6的字符串变量替换功能,这样可以取代字符串拼接
1234//goodlet name = 'weiwei';let time = '22:00';let message = `Hello ${name}, it's ${time} now`;
ES6函数参数增强
推荐使用默认值、剩余参数等功能,这能让你的函数声明和调用变得更为简洁
12345678910111213141516171819var foo = (x = 1) => x + 1;foo(); // 2var extend = (source, ...args) => {for (let target in args) {for (let name in Object.keys(target) {if (!source.hasOwnProperty(name) {source[name] = target[name];}}}};var extensions = [{name: 'Zhang'},{age: 17},{work: 'hard'}];extend({}, ...extensions);
ES6新增关键字let和const
推荐使用
let
全面代替var
,因为它创建了块级作用域变量(变量只在代码块内生效),尤其是for
循环12345for(let i = 0; i < 10; i++) {foo[i].onclick = function() {console.log(i);};}
建议自由在逻辑上是常量的情况才使用
const
,它代表常量,定的同时必须赋值123456// goodconst MAX_CAT_SIZE_KG = 3000;// badMAX_CAT_SIZE_KG = 5000; // SyntaxErrorMAX_CAT_SIZE_KG++; // nice try, but still a SyntaxError
ES6迭代器和for..of
推荐使用
for..of
来迭代集合对象(Array, Map, Set, arguments对象)的值1234// goodfor (let item of array) {// do somehting}
- 避免使用
for...in
来迭代结合对象,它通常用于迭代对象的属性名
ES6生成器
谨慎使用生成器,异步控制器的未来是
async
和await
这两个关键字123456789// goodasync function save(Something) {try {await Something.save(); // 等待await后面的代码执行完,类似于yield} catch (ex) {//error handling}console.log('success');}
ES6模块
- 谨慎使用ES6的模块系统,Node项目建议使用CommonJS方案,因为ES6并没有包括模块加载器规范,参考文章
- 或者使用ES6的模块定义,但使用ADM作为运行时模块解决方案
- 保持使用
import
和export
进行模块的引入和定义,可以安全地使用命名export
和默认export
- 在使用Babel转换时,配置
modules: 'amd'
转换为AMD的模块定义 - 不要依赖
SystemJS
这样的ES6模块加载器
- 保持使用
ES6新增集合Map和Set
- 当你的元素或者键值有可能不是字符串时,无条件地使用
Map
和Set
- 有移除操作的需求时,使用
Map
和Set
- 当仅需要一个不可重复的集合时,使用
Set
优先于普通对象,而不要使用{foo: true}
这样的对象 - 当需要遍历功能时,使用
Map
和Set
,因为其可以简单地使用for..of
进行遍历 WeakMap
和WeakSet
是没有办法模拟实现的,因此不要使用
ES6 Promise
建议所有异步均使用Promise实现
12345678910// 构造一个Promise实例var promise = new Promise(function(resolve, reject) {// ... some codeif (/* 异步操作成功 */){resolve(value);} else {reject(error);}});
Promise实例生成后,可以用
then
方法分别制定Resolved状态和Reject状态的回调函数12345promise.then(function(value) {// success}, function(value) {// failure});
推荐的书
- 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
- Secrets of the JavaScript Ninja - John Resig and Bear Bibeault
- Human JavaScript - Henrik Joreteg
- Superhero.js - Kim Joar Bekkelund, Mads Mobæk, & Olav Bjorkoy
- JSBooks
- Third Party JavaScript - Ben Vinegar and Anton Kovalyov
推荐的博客
- DailyJS
- JavaScript Weekly
- JavaScript, JavaScript…
- Bocoup Weblog
- Adequately Good
- NCZOnline
- Perfection Kills
- Ben Alman
- Dmitry Baranovskiy
- Dustin Diaz
- nettuts
The JavaScript Style Guide Guide