前端编码规范

通过几个迭代后,前端代码变得越来越臃肿越来越乱。所以编码规范就排上日程。

结合这段时间我们的经验加上从网上搜集到的实用的编码规范,做一个简单整理。

前端编码规范意见稿

  • 统一ESLint文件
    • 比如用Airbnb,随着积累可以在其基础上进行扩展。
  • React组件
    • 如果组件需要维护自己的state或者使用其生命周期方法则用class,除此以外都用function。
  • Redux
    • 除了纯渲染组件(没有复杂的交互、逻辑),其余都用redux
              redux使代码结构更加清晰,可读性较强便于维护(倒逼组件或者模块拆的更加合理)。
              redux可当成全局内存库来用,当没有更新state时,无论何时何地都能到一样的数据,便于通用组件的开发
              redux可以减少数据的传递,不用依次往下传,随用随取,特别是针对组件层级比较深的情况
      
  • 统一格式化插件(如果要用格式化插件)
    • 比如VSCode的prettier或者beauty,千万避免多人用多套格式化插件的情况

JS开发规范

对象

  • 不要使用关键字作为key或者属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    var user = {
    private: true
    };

    // good
    var user = {
    hidden: true
    };

数组

  • 如果你不知道数组的长度,使用push

    1
    2
    3
    4
    5
    6
    7
    8
    var userArr = [];


    // bad
    userArr[index] = 'gamehu';

    // good
    userArr.push('gamehu');
  • 当你需要拷贝数组时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var len = users.length,
    usersCopy = [],
    i;

    // bad
    for (i = 0; i < len; i++) {
    usersCopy[i] = items[i];
    }

    // good
    usersCopy = users.slice();

    //good ES6

    usersCopy = [...users];

    usersCopy=Array.from(users);


  • 将类数组的对象转成数组.

    1
    2
    3
    4
    5
    let args = [].slice.apply(users);

    // ES6
    let args=Array.from(users);

字符串

  • 对字符串使用单引号 ‘’
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // bad
    var name = "Gamehu";

    // good
    var name = 'Gamehu';

    // bad
    var fullName = "Gamehu " + this.lastName;

    // good
    var fullName = 'Gamehu ' + this.lastName;

函数

  • 绝对不要把参数命名为 arguments, 将覆盖作用域内传过来的 arguments 对象.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    function nope(name, options, arguments) {
    // ...stuff...
    }

    // good
    function yup(name, options, args) {
    // ...stuff...
    }
  • 当函数的参数特别多的时候用对象封装起来再传递.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    //bad

    export const resourceStoreDynamicFormForEdit = (group, data, index, form, editableIndex, sortItems, formItemLayout, validatorMap, dataMap, ciType, selectedCiEditable, showAlarmSlowStrategy) => {}

    // good

    const params={};

    params={
    group:group, xxx
    }

    export const resourceStoreDynamicFormForEdit=(params) ={
    }

  • 函数应该只在一个抽象层次上做一件事.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    // bad
    function getUserRouteHandler (req, res) {
    const { userId } = req.params
    // inline SQL query
    knex('user')
    .where({ id: userId })
    .first()
    .then((user) => res.json(user))
    }

    // good
    // User model (eg. models/user.js)
    const tableName = 'user'
    const User = {
    getOne (userId) {
    return knex(tableName)
    .where({ id: userId })
    .first()
    }
    }

    // route handler (eg. server/routes/user/get.js)
    function getUserRouteHandler (req, res) {
    const { userId } = req.params
    User.getOne(userId)
    .then((user) => res.json(user))
    }


  • 更高层次的函数在低层次函数的前面,便于阅读.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // bad
    // "I need the full name for something..."
    function getFullName (user) {
    return `${user.firstName} ${user.lastName}`
    }

    function renderEmailTemplate (user) {
    // "oh, here"
    const fullName = getFullName(user)
    return `Dear ${fullName}, ...`
    }

    // good
    function renderEmailTemplate (user) {
    // "I need the full name of the user"
    const fullName = getFullName(user)
    return `Dear ${fullName}, ...`
    }

    // "I use this for the email template rendering"
    function getFullName (user) {
    return `${user.firstName} ${user.lastName}`
    }
  • 声明函数时最好设置默认值.

    1
    2
    3
    //es6
    function (a=1, b=1) { // function code }

  • 如果想避免var变量造成的命名冲突,不存在特殊场景时可考虑使用立即执行函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字,
    就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:
    (function() {
    // 代码
    // ...
    })();
    function(){…}是一个匿名函数,包围它的一对括号将其转换为一个表达式,
    紧跟其后的一对括号调用了这个函数。立即执行函数也可以理解为立即调用一个匿名函数。
    立即执行函数最常见的应用场景就是:将var变量的作用域限制于你们函数内,这样可以避免命名冲突。


  • 当有场景需要使用私有属性时,使用闭包定义私有变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function Product() {

    var name;

    this.setName = function(value) {
    name = value;
    };

    this.getName = function() {
    return name;
    };
    }

    var p = new Product();
    p.setName("Fundebug");

    console.log(p.name); // 输出undefined
    console.log(p.getName()); // 输出Fundebug
    代码中,对象p的的name属性为私有属性,使用p.name不能直接访问。


变量

  • 总是使用 let、const、var来声明变量,如果不这么做将导致产生全局变量,我们要避免污染全局命名空间。

    1
    2
    3
    4
    5
    // bad
    superPower = new SuperPower();

    // good
    var superPower = new SuperPower();
  • 使用一个 let 以及新行声明多个变量,缩进4个空格。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    let items = getItems();
    let goSportsTeam = true;
    let dragonball = 'z';

    // good
    let items = getItems(),
    goSportsTeam = true,
    dragonball = 'z';
  • 最后再声明未赋值的变量,当你想引用之前已赋值变量的时候很有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // bad
    var i, len,hidden = true,
    items = getItems();

    // bad
    var i, items = getItems(),
    hidden = true,
    len;

    // good
    var items = getItems(),
    hidden = true,
    length,
    i;
  • 在作用域顶部声明变量,避免变量声明和赋值引起的相关问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    // 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;
    }
  • 在声明变量时初始化变量。

    1
    2
    3
    4
    5
    6
    7
    8
    var firstName = "",
    lastName = "",
    price = 0,
    discount = 0,
    fullPrice = 0,
    myArray = [],
    myObject = {};

  • 在声明变量时别用对象。

    1
    2
    3
    4
    5
    6
    7
    8
    Use {} instead of new Object()
    Use "" instead of new String()
    Use 0 instead of new Number()
    Use false instead of new Boolean()
    Use [] instead of new Array()
    Use /()/ instead of new RegExp()
    Use function (){} instead of new Function()

  • 用===代替==,因为==会在比较之前进行类型转换。

    1
    2
    3
    4
    5
    6
    7
    8
    0 == "";        // true
    1 == "1"; // true
    1 == true; // true

    0 === ""; // false
    1 === "1"; // false
    1 === true; // false

  • 注意数字和字符串之间的转换。

    1
    2
    3
    4
    5
    6
    7
    8
    var x = 5 + 7;       // x.valueOf() is 12,  typeof x is a number
    var x = 5 + "7"; // x.valueOf() is 57, typeof x is a string
    var x = "5" + 7; // x.valueOf() is 57, typeof x is a string
    var x = 5 - 7; // x.valueOf() is -2, typeof x is a number
    var x = 5 - "7"; // x.valueOf() is -2, typeof x is a number
    var x = "5" - 7; // x.valueOf() is -2, typeof x is a number
    var x = 5 - "x"; // x.valueOf() is NaN, typeof x is a number

  • 在for循环的每次迭代中都不要让JavaScript读取数组的长度。 将长度值存储在另一个变量中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //bad
    var names = ['George',
    'Ringo',
    'Paul',
    'John'];
    for(var i=0;i<names.length;i++){
    doSomethingWith(names[i]);
    }

    //good
    var names = ['George',
    'Ringo',
    'Paul',
    'John'];
    for(var i=0,j=names.length;i<j;i++){
    doSomethingWith(names[i]);
    }

条件表达式和等号

  • 合理使用 === 和 !== 以及 == 和 !=.

  • 合理使用表达式逻辑操作运算

  • 条件表达式的强制类型转换遵循以下规则:

  • switch时一定要用default结束

    1
    2
    3
    4
    5
    6
    7
    1: 对象 被计算为 true
    2: Undefined 被计算为 false
    3: Null 被计算为 false
    4: 布尔值 被计算为 布尔的值
    5: 数字 如果是 +0, -0, or NaN 被计算为 false , 否则为 true
    6: 字符串 如果是空字符串 '' 则被计算为 false, 否则为 true

  • 使用快捷方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // bad
    if (name !== '') {
    // ...stuff...
    }

    // good
    if (name) {
    // ...stuff...
    }

    // bad
    if (collection.length > 0) {
    // ...stuff...
    }

    // good
    if (collection.length) {
    // ...stuff...
    }

    //bad
    if(v){
    var x = v;
    } else {
    var x =10;
    }

    //good
    var x = v || 10;


    //bad
    var direction;
    if(x > 100){
    direction = 1;
    } else {
    direction = -1;
    }

    //good
    var direction = (x > 100) ? 1 : -1;


  • 给所有多行的块使用大括号
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // bad
    if (test)
    return false;

    // good
    if (test) return false;

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

    // bad
    function() { return false; }

    // good
    function() {
    return false;
    }

注释

  • 使用 /** … */ 进行多行注释,包括描述,指定类型以及参数值和返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     // 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;
    }
  • 使用 // 进行单行注释,在评论对象的上面进行单行注释,注释前放一个空行.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     // 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 帮助其他人迅速理解

    1
    2
    3
    4
    5
    6
    7
     function Calculator() {

    // FIXME: shouldn't use a global here
    total = 0;

    return this;
    }
    1
    2
    3
    4
    5
    6
    7
     function Calculator() {

    // TODO: total should be configurable by an options param
    this.total = 0;

    return this;
    }

空白

  • 缩进、格式化能帮助团队更快得定位修复代码BUG.

  • 将tab设为4个空格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // bad
    function() {
    ∙∙var name;
    }

    // bad
    function() {
    ∙var name;
    }

    // good
    function() {
    ∙∙∙∙var name;
    }
  • 大括号前放一个空格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    {
    // 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'
    });
    }
  • 在做长方法链时使用缩进.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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);

逗号

  • 不要将逗号放前面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // bad
    var once
    , upon
    , aTime;

    // good
    var once,
    upon,
    aTime;

    // bad
    var hero = {
    firstName: 'Bob'
    , lastName: 'Parr'
    , heroName: 'Mr. Incredible'
    , superPower: 'strength'
    };

    // good
    var hero = {
    firstName: 'Bob',
    lastName: 'Parr',
    heroName: 'Mr. Incredible',
    superPower: 'strength'
    };
  • 不要加多余的逗号,这可能会在IE下引起错误,同时如果多一个逗号某些ES3的实现会计算多数组的长度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // bad
    var hero = {
    firstName: 'Kevin',
    lastName: 'Flynn',
    };

    var heroes = [
    'Batman',
    'Superman',
    ];

    // good
    var hero = {
    firstName: 'Kevin',
    lastName: 'Flynn'
    };

    var heroes = [
    'Batman',
    'Superman'
    ];

分号

  • 语句结束一定要加分号
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // bad
    (function() {
    var name = 'Skywalker'
    return name
    })()

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

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

类型转换

  • 在语句的开始执行类型转换.

  • 字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //  => 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 并且总是带上类型转换的基数.,如parseInt(value, 10)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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;
  • 布尔值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var age = 0;

    // bad
    var hasAge = new Boolean(age);

    // good
    var hasAge = Boolean(age);

    // good
    var hasAge = !!age;

命名约定

  • 避免单个字符名,让你的变量名有描述意义。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    function q() {
    // ...stuff...
    }

    // good
    function query() {
    // ..stuff..
    }
  • 当命名对象、函数和实例时使用驼峰命名规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 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'
    });
  • 当命名构造函数或类时使用驼峰式大写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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'
    });
  • 命名私有属性时前面加个下划线 _

    1
    2
    3
    4
    5
    6
    // bad
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';

    // good
    this._firstName = 'Panda';
  • 模块开发的常量定义必须包含“完整的模块名称“

    1
    2
    3
    4
    5
    6
    // bad
    export const GET_ASSET_LIST = 'GET_ASSET_LIST';

    // good
    export const GET_ASSET_LIST = 'ALARM_GET_ASSET_LIST';

  • 望名知意,建议驼峰命名(函数也适用)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    // bad
    let fItem;

    // good
    let formItem;

    //bad
    let yUnit = unitObj.unit;

    //good
    let yAxisUnit = unitObj.unit;

存取器

  • 属性的存取器函数不是必需的
  • 如果你确实有存取器函数的话使用getVal() 和 setVal(‘hello’),java getter、setter风格或者jQuery风格
  • 如果属性是布尔值,使用isVal() 或 hasVal()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // bad
    if (!dragon.age()) {
    return false;
    }

    // good
    if (!dragon.hasAge()) {
    return false;
    }
  • 可以创建get()和set()函数,但是要保持一致
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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];
    };

构造器

  • 给对象原型分配方法,而不是用一个新的对象覆盖原型,覆盖原型会使继承出现问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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 帮助方法可链。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 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()方法,但是确保它工作正常并且不会有副作用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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();
    };

事件

  • 当给事件附加数据时,传入一个哈希而不是原始值,这可以让后面维护时加入更多数据到事件数据里而不用找出并更新那个事件的事件处理器
    1
    2
    3
    4
    5
    6
    7
    8
    // bad
    $(this).trigger('listingUpdated', listing.id);

    ...

    $(this).on('listingUpdated', function(e, listingId) {
    // do something with listingId
    });
  • 更好:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // good
    $(this).trigger('listingUpdated', { listingId : listing.id });

    ...

    $(this).on('listingUpdated', function(e, data) {
    // do something with data.listingId
    });

模块

  • 文件应该以驼峰命名,并在同名文件夹下,同时导出的时候名字一致

参考