Node.js 风格指南

jopen 5年前

  • 参考资料
  • 类型
  • 对象
  • 数组
  • 字符串
  • 函数
  • 属性
  • 变量
  • Requires
  • 回调函数
  • Try catch
  • 提升
  • 条件表达式 & 相等性
  • 代码块
  • 注释
  • 空格
  • 逗号
  • 分号作为语句块的结束
  • 类型转换 & 强制类型转换
  • 命名约定
  • 访问器
  • 构造函数
  • ES6箭头函数
  • ES6增强的对象字面量
  • ES6模板字符串
  • ES6函数参数增强
  • ES6新增关键字let和const
  • ES6迭代器和for..of
  • ES6生成器
  • ES6模块
  • ES6新增集合Map和Set
  • ES6 Promise
  • 推荐的书
  • 推荐的博客 </li> </ul>

    参考资料

    类型

    • 原始类型: 当你访问一个原始数据类型时,你直接操作的是它的值

      • string
      • number
      • boolean
      • null
      • undefined
      var foo = 1; var bar = foo;    bar = 9; console.log(foo, bar); // => 1, 9
      </li> </ul>
      • 复杂情况: 当你访问的是一个复杂类型时,你操作的是它的引用

        • object
        • array
        • function
        var foo = [1, 2]; var bar = foo;    bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
        </li> </ul>

        ⬆ back to top

        对象

        • 在对象创建时使用字面量语法

          // bad var item = new Object(); // good var item = {};
        • 使用可读的别名,避免使用保留字

          // bad var superman = {    class: 'alien' }; // bad var superman = {    klass: 'alien' }; // good var superman = {    type: 'alien' };

        ⬆ back to top

        数组

        • 使用字面量语法创建数组

          // bad var items = new Array(); // good var items = [];
        • 当你不知道数组的长度时使用Array#push.

          var someStack = []; // bad someStack[someStack.length] = 'abracadabra'; // good someStack.push('abracadabra');
        • 当你需要复制数据时使用Array#slice. jsPerf

          var len = items.length; var itemsCopy = []; var i; // bad for (i = 0; i < len; i++) {    itemsCopy[i] = items[i];  } // good itemsCopy = items.slice();
        • 将一个类数组对象转为数组,使用Array#slice.

          function trigger() { var args = Array.prototype.slice.call(arguments);    ...  }

        ⬆ back to top

        字符串

        • 使用单引号''表示字符串

          // bad var name = "Bob Parr"; // good var name = 'Bob Parr'; // bad var fullName = "Bob " + this.lastName; // good var fullName = 'Bob ' + this.lastName;
        • 长于80个字符的字符串应该被写成多行(使用字符串拼接或ES6的模板字符串)

        • 注意:如果过度使用,长字符串拼接会影响性能. jsPerf & Discussion

          // 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.'; // good var errorMessage = `  hello  world  this  is `;
        • 当使用程序来生成字符串时,使用Array#join,而不是字符串拼接.

          var 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; // bad function inbox(messages) {    items = '<ul>'; for (i = 0; i < length; i++) {      items += '<li>' + messages[i].message + '</li>';    } return items + '</ul>';  } // good function inbox(messages) {    items = []; for (i = 0; i < length; i++) {      items[i] = messages[i].message;    } return '<ul><li>' + items.join('</li><li>') + '</li></ul>';  }

        ⬆ back to top

        函数

        • 函数表达式:

          // 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.');  })();
        • 永远不要再一个非函数语句块内使用函数声明(if, while, 等). 将函数赋值给一个变量.

          // bad if (currentUser) { function test() { console.log('Nope.');    }  } // good var test; if (currentUser) { test = function test() { console.log('Yup.');    };  }
        • 永远不要将你的参数命名为arguments, 这会与函数范围内的arguments对象冲突.

          // bad function nope(name, options, arguments) { // ...stuff... } // good function yup(name, options, args) { // ...stuff... }

        ⬆ back to top

        属性

        • 当访问属性时请使用.操作符.

          var luke = {    jedi: true,    age: 28 }; // bad var isJedi = luke['jedi']; // good var isJedi = luke.jedi;
        • 当你要用变量访问属性时,请使用[].

          var luke = {    jedi: true,    age: 28 }; function getProp(prop) { return luke[prop];  } var isJedi = getProp('jedi');

        ⬆ back to top

        变量

        • 始终使用var来声明变量. 否则会创建全局变量,污染全局命名空间.

          // bad superPower = new SuperPower(); // good var superPower = new SuperPower();
        • 声明变量时使用一个新行, 并且每一行都使用var来声明.

          // bad var items = getItems(),        goSportsTeam = true,        dragonball = 'z'; // good var items = getItems(); var goSportsTeam = true; var dragonball = 'z';
        • 最后声明未赋值的便利. 后面如果你要使用前面的变量进行赋值时会显得很便利.

          // bad var i; var items = getItems(); var dragonball; var goSportsTeam = true; var len; // good var items = getItems(); var goSportsTeam = true; var dragonball; var length; var i;
        • 避免冗余的变量声明, 可已使用Object对象来构建命名空间.

          // bad var kaleidoscopeName = '..'; var kaleidoscopeLens = []; var kaleidoscopeColors = []; // good var kaleidoscope = {    name: '..',    lens: [],    colors: []  };
        • 在变量作用范围的最顶端声明变量. 这可以帮你避免赋值提升的问题.

          // 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;  }

        Requires

        • 使用如下顺序来组织node代码中的require语句:

          • core modules
          • npm modules
          • others
          // bad var Car = require('./models/Car'); var async = require('async'); var http = require('http'); // good var http = require('http'); var fs = require('fs'); var async = require('async'); var mongoose = require('mongoose'); var Car = require('./models/Car');
          </li>
        • 当你引入模块时,不要加.js后缀

            // bad var Batmobil = require('./models/Car.js'); // good var Batmobil = require('./models/Car');
        • </ul>

          ⬆ back to top

          回调函数

          • 在回调函数中要始终检测错误

            //bad database.get('pokemons', function(err, pokemons) { console.log(pokemons);  }); //good database.get('drabonballs', function(err, drabonballs) { if (err) { // handle the error somehow, maybe return with a callback return console.log(err);    } console.log(drabonballs);  });
          • 遇到错误时从回调中返回

            //bad database.get('drabonballs', function(err, drabonballs) { if (err) { // if not return here console.log(err);    } // this line will be executed as well console.log(drabonballs);  }); //good database.get('drabonballs', function(err, drabonballs) { if (err) { // handle the error somehow, maybe return with a callback return console.log(err);    } console.log(drabonballs);  });
          • 当你要开发接口给外部时,在你的回调函数中使用描述性的参数。它能够让你的代码更可读。

            // bad function getAnimals(done) {    Animal.get(done);  } // good function getAnimals(done) {    Animal.get(function(err, animals) { if(err) { return done(err);      } return done(null, {        dogs: animals.dogs,        cats: animals.cats      })    });  }

          ⬆ back to top

          Try catch

          • 只能在同步函数中使用throw

            Try-catch 语句块不能被用在异步代码块中。

            //bad function readPackageJson (callback) {    fs.readFile('package.json', function(err, file) { if (err) { throw err;      }      ...    });  } //good function readPackageJson (callback) {    fs.readFile('package.json', function(err, file) { if (err) { return callback(err);      }      ...    });  }
          • 在同步调用中捕获错误,JSON.parse()应该使用try-catch语句块

            //bad var data = JSON.parse(jsonAsAString); //good var data; try {    data = JSON.parse(jsonAsAString);  } catch (e) { //handle error - hopefully not with a console.log ;) console.log(e);  }

          ⬆ back to top

          提升

          • 变量声明会被提升到作用域的顶端,而赋值操作则不会。

            // 先看个简单的例子,显然它会抛出错误 function example() { console.log(notDefined); // => throws a ReferenceError } // 我们先使用了一个变量,而后再声明并初始化这个变量 // 输出结果没有报错,而是 `undefined`,意思是未被初始化 function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true;  } // 变量声明部分会被提升,赋值部分仍保持不变 // 上面的代码等同于 function example() { var declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true;  }
          • 匿名函数表达式会提升它们的变量名,但是函数赋值部门不会被提升

            function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function() { console.log('anonymous function expression');    };  }
          • 命名函数表达式会提升它们的变量名,但函数名或函数体不会被提升。

            function example() { console.log(named); // => undefined named(); // => TypeError named is not a function superPower(); // => ReferenceError superPower is not defined var 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); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named');    }  }
          • 函数声明会被整体提升到作用域顶端

            function example() {    superPower(); // => Flying function superPower() { console.log('Flying');    }  }
          • 更多信息请参考 JavaScript Scoping & Hoisting by Ben Cherry

          ⬆ back to top

          条件表达式 & 相等性

          • 使用===和!==来代替==和!=.
          • 条件表达式会被使用ToBoolean方法进行强制类型转换。并且服从如下规则:

            • Objects 被转换为 true
            • Undefined 被转换为 false
            • Null 被转换为 false
            • Booleans 被转换为 实际的boolean值
            • Numbers 被转换为 false 如果是 +0, -0, or NaN, 其他都为 true
            • Strings 被转换为 false 如果是空字符串'', 其他都为 true
            if ([0]) { // true // 数组是对象,对象始终被转换为 `true` }
            </li>
          • 使用缩减版.

            // bad if (name !== '') { // ...stuff... } // good if (name) { // ...stuff... } // bad if (collection.length > 0) { // ...stuff... } // good if (collection.length) { // ...stuff... }
          • 更多信息请参考 Truth Equality and JavaScript by Angus Croll

          • </ul>

            ⬆ back to top

            代码块

            • 所有的多行代码块都要使用大括号,并且不要写在一行

              // bad if (test) return false; // bad if (test) return false; // good if (test) { return false;  } // bad function() { return false; } // good function() { return false;  }

            ⬆ back to top

            注释

            • 使用/** ... */进行多行注释. 请在你们加入注释说明,指明参数和返回值的类型

              // 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;  }
            • 使用//进行单行注释. 请用一个新行来添加注释。并在注释行前增加一个空行。

              // 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;  }
            • 如果是为了指明一个错误,请在你的注释前加上FIXME或TODO前缀来帮助其他开发者快速的了解你的注释意图。其中FIXME可以表示这个问题需要解决,或者TODO来表示需要被实现的功能块。

            • 使用// FIXME:来注解一个问题。

              function Calculator() { // FIXME: shouldn't use a global here total = 0; return this;  }
            • 使用// TODO:来注解一个需要被实现(完成)的任务。

              function Calculator() { // TODO: total should be configurable by an options param this.total = 0; return this;  }

            ⬆ back to top

            空格

            • 推荐使用2个空格作为缩进

              // bad function() {  ∙∙∙∙var name;  } // bad function() {  ∙var name;  } // good function() {  ∙∙var name;  }
            • 在所有起始的大括号前加一个空格

              // 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' });
            • 在操作符见使用一个空格

              // bad var x=y+5; // good var x = y + 5;
            • 文件结束后增加一个空行

              // bad (function(global) { // ...stuff... })(this);
              // bad (function(global) { // ...stuff... })(this);↵  ↵
              // good (function(global) { // ...stuff... })(this);↵
            • 对链接起来的方法使用缩进成多行的形式

              // 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);

            ⬆ back to top

            逗号

            • 推荐的做法是逗号在每一行的末尾

              // bad var hero = {      firstName: 'Bob' , lastName: 'Parr' , heroName: 'Mr. Incredible' , superPower: 'strength' }; // good var hero = {    firstName: 'Bob',    lastName: 'Parr',    heroName: 'Mr. Incredible',    superPower: 'strength' };
            • 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):

              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.

                // bad var hero = {      firstName: 'Kevin',      lastName: 'Flynn',    }; var heroes = [ 'Batman', 'Superman',    ]; // good var hero = {      firstName: 'Kevin',      lastName: 'Flynn' }; var heroes = [ 'Batman', 'Superman' ];

            ⬆ back to top

            分号作为语句块的结束

            • Yup.

              // bad (function() { var name = 'Skywalker' return name  })() // good (function() { var name = 'Skywalker'; return name;  })(); // good ;(function() { var name = 'Skywalker'; return name;  })();

            ⬆ back to top

            类型转换 & 强制类型转换

            • 在声明语句的最前端执行强制类型转换.
            • 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';
            • 使用parseInt来进行整数的类型转换,并且始终提供一个基数.

              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);
            • If for whatever reason you are doing something wild andparseIntis your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you're doing.

              // 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: Be careful when using bitshift operations. Numbers are represented as 64-bit values, but Bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Discussion. Largest signed 32-bit Int is 2,147,483,647:

              2147483647 >> 0 //=> 2147483647 2147483648 >> 0 //=> -2147483648 2147483649 >> 0 //=> -2147483647
            • Booleans:

              var age = 0; // bad var hasAge = new Boolean(age); // good var hasAge = Boolean(age); // good var hasAge = !!age;

            ⬆ back to top

            命名约定

            • 避免使用当个字符命名,使用描述性的名字:

              // bad function q() { // ...stuff... } // good function query() { // ..stuff.. }
            • 对于对象、函数、和实例采用小驼峰(camelCase)命名法

              // bad var OBJEcttsssss = {}; 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' });
            • 当命名类或构造函数时使用大驼峰或Pascal命名法(PascalCase)

              // 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' });
            • 在私有属性前加上一个_前缀

              // bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; // good this._firstName = 'Panda';
            • 当你要保存this值时,可以将其命名为_this.

              // bad function() { var self = this; return function() { console.log(self);    };  } // bad function() { var that = this; return function() { console.log(that);    };  } // good function() { var _this = this; return function() { console.log(_this);    };  }
            • 命名你的函数。这将有助于堆栈跟踪。

              // bad var log = function(msg) { console.log(msg);  }; // good var log = function log(msg) { console.log(msg);  };

            ⬆ back to top

            访问器

            • 属性访问器并不是必须的。
            • 如果你确实需要,请命名为 getVal() 和 setVal('hello') 的形式

              // bad dragon.age(); // good dragon.getAge(); // bad dragon.age(25); // good dragon.setAge(25);
            • 如果属性是一个布尔值,请使用 isVal() 或 hasVal()

              // bad if (!dragon.age()) { return false;  } // good if (!dragon.hasAge()) { return false;  }
            • 你也可以创建 get() 和 set() 函数, 但一定要保持一致.

              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];  };

            ⬆ back to top

            构造函数

            • 在原型链上增加属性,而不是覆写原型链。

              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');  };
            • 你可以在方法中返回this从而来构建可链接的方法。

              // 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);
            • 你可以创建一个自定义的toString()方法,但是你要确保它能正常工作并且没有其他副作用。

              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();  };

            ⬆ back to top

            ES6箭头函数

            • 所有的Arrow Function的参数均使用()包裹,即便只有一个参数:

              // good let foo = (x) => x + 1; // bad let foo = x => x + 1;
            • 定义函数尽量使用Arrow functions,而不是function关键字

              // good let foo = () => { // code  }; // bad function foo() { // code  } // bad let foo = function () { // code  }

              除非当前场景不适合使用Arrow Function,如函数表达式需要自递归、需要运行时可变的this对象等。

            • 对于对象、类中的方法,使用增强的对象字面量

              // good let foo = { bar () { // code  }  } // bad let foo = {    bar: () => { // code  }  } // bad let foo = { bar: function () { // code }  }

            ES6增强的对象字面量

            • 可以在对象总直接定义方法

              // good let foo = { bar() { // code  }  }
            • 可使用通过计算得出的键值

              // 当你需要的时候使用 let MY_KEY = 'bar'; let foo = {    [MY_KEY + 'Hash']: 123 }
            • 与当前scope中同名变量的简写

              // bad let bar = 'bar'; let foo = {      bar // 相当于bar: bar };

            ES6模板字符串

            • 不推荐使用多行字符串,因为不方便代码缩进

              // bad let html = `<div>  <p>Hello world</p> </div>`
            • 推荐使用ES6的字符串变量替换功能,这样可以取代字符串拼接

              //good let name = 'weiwei'; let time = '22:00'; let message = `Hello ${name}, it's ${time} now`;

            ES6函数参数增强

            • 推荐使用默认值、剩余参数等功能,这能让你的函数声明和调用变得更为简洁

              var foo = (x = 1) => x + 1;  foo(); // 2 var 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,因为它创建了块级作用域变量
            • 建议自由在逻辑上是常量的情况才使用const

            ES6迭代器和for..of

            • 推荐使用for..of来迭代集合

              // good for (let item in array) {    }

            ES6生成器

            • 谨慎使用生成器,异步控制器的未来是async和await这两个关键字

              // good async function save(Something) { try { await Something.save(); // 等待await后面的代码执行完,类似于yield } catch (ex) { //error handling } console.log('success');  }

            ES6模块