这是《 javaScript设计模式与开发实践 》一书的最后一章"代码重构"。

以下的一些方法不是必须严格遵守的标准,选择实践哪些,以及如何实现这都需根据情况而定(是不是有充足时间)

提炼函数

如果在函数中有一段代码可以独立出来,那么最好把这些代码放进另外一个独立的函数当中去。好处有:

  • 避免出现超大型函数。

  • 独立出来的函数有助于代码复用。

  • 独立出来的函数更加容易被改写,减少维护成本。

  • 独立出来的函数如果有一个良好的命名,本身就起到了注释的作用。

例如一个获取用户信息的函数,我们还需要打印用户信息,这种情况下就可以独立出打印信息的代码。

var getUserInfo = function() {
    ajax('http://xxx/userInfo', function(data) {
        console.log('userId: ' + data.userId);
        console.log('userName: ' + data.userName);
        console.log('nickName: ' + data.nickName);
    });
};

改写:

var getUserInfo = function() {
    ajax('http://xxx/userInfo', function(data) {
        printUserInfo(data);
    });
};

var printUserInfo = function(data) {
    console.log('userId: ' + data.userId);
    console.log('userName: ' + data.userName);
    console.log('nickName: ' + data.nickName);
};

合并重复代码片段

如果一个函数内有一些条件语句,而条件语句内散布了一些重复代码,就有必要进行合并去重工作。

例如一个分页函数paging,函数接受一个currpage表示挑战页码,在跳转前需要判断currpage是否在有效的取值范围。

var paging = function(currpage) {
    if (currpage <= 0) {
        currpage = 0;
        jump(currpage); // 跳转
    } else if (currpage >= totalPage) { // 总页数totalPage
        currpage = totalPage;
        jump(currpage); // 跳转
    } else {
        jump(currpage); // 跳转
    }
}

负责跳转的jump(currpage)在每个条件分支都出现了,所以完全把这句代码独立出来:

var paging = function(currpage) {
    if (currpage <= 0) {
        currpage = 0;
    } else if (currpage >= totalPage) { // 总页数totalPage
        currpage = totalPage;
    }

    jump(currpage); // 跳转
}

把条件语句提炼成函数

在程序设计中,复杂的条件语句是导致程序难以阅读和理解的重要原因,而且容易增大函数代码量。例如以一个计算商品价格的getPrice函数,商品计算有个规则,夏天商品以8折出售。

var getPrice = function(price) {
    var date = new Date;
    if (date.getMonth() >= 6 && date.getMonth() <= 9 ) { // 处于夏天
        return price * 0.8
    }
    return price;
}

其中的条件语句if (date.getMonth() >= 6 && date.getMonth() <= 9 )
如果改写提炼成一个独立的函数,既能更准确的表达代码的意思,函数名又能起到注释作用。

var isSummer = function() {
    var dateMonth = (new Date).getMonth();
    return dateMonth >= 6 && dateMonth <= 9 ;
};

var getPrice = function(price) {
    var date = new Date;
    if ( isSummer() ) { // 处于夏天
        return price * 0.8
    }
    return price;
};

合理使用循环

在函数体内,如果有些代码是负责一些重复性的工作,那么合理使用循环不仅可以完成同样的功能,还可以使代码量更少,有一段创建XHR对象的代码,为了简化代码,只检测IE9已下的浏览器。

var creatXHR = function() {
    var xhr;
    try{
        xhr = new ActiveXObject('MSXML2.XMLHTTP.6.0');
    } catch(e) {
        try{
            xhr = new ActiveXObject('MSXML2.XMLHTTP.3.0');
        } catch(e) {
            xhr = new ActiveXObject('MSXML2.XMLHTTP');
        }
    }

    return xhr;
};

var xhr = creatXHR();

下面灵活的使用循环,可以得到上面代码一样的效果:

var creatXHR = function() {
    var versions = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
    for (var i = 0;i < versions.length; i++) {
        try{
            return new ActiveObject( version[i] );
        }catch(e) {

        }
    }
};

var xhr = creatXHR();

提前让函数退出嵌套条件分支

初学者可能有这样一个观念:”每个函数只能有一个入口和一个出口。”现代编程语言都会限制函数有一个入口。但是关于”函数只有一个出口”,往往有不一样的看法。
下面是一个遵守“函数只有一个出口”的代码。

var del = fucntion(obj) {
    var ret;
    if (!obj.isReadOnly) { // 不为只读才能删除
        if (obj.isFolder) { // 判断文件夹
            ret = deletFolder(obj);
        } else if (obj.isFile) { // 判断文件
            ret = deletFile(obj);
        }
    }
    return ret;
}

嵌套的条件分支语句是代码维护者的噩梦,如果对函数的剩余部分不感兴趣,那就应该立即退出。
我们可以挑选一些条件分支,在进入这些条件分支后,就立即让函数退出。有一个常见的技巧。在面对一个嵌套的if分支时,我们可以把外层if表达式进行反转。例如:

var del = function(obj) {
    if (obj.isReadOnly) { // 反转表达式
        return;
    }

    if (obj.isFolder) {
        return deletFolder(obj);
    }

    if (obj.isFile) {
        return deletFile(obj);
    }
}

传递对象参数代替过长的参数列表

函数可能接受多个参数,参数越多函数就越难理解和使用。

setUserInfo(1111, 'sven', 'hangzhou', 'male', '137*****')

// 可改写成
setUserInfo({
    id: 1111,
    name: 'sven',
    address: 'hangzhou',
    sex: 'male',
    mobile: '137*****'
})

改写后可以不再关心参数的数量和顺序,一般参数控制在4个以内,超过4个就需要转化成对象形式。

合理使用链式调用

链式调用在调试的时候非常不方便,如果一条调用链中有错误出现,必须要把这条调用链拆开才能定位错误出现的地方。

如果该链条的结构稳定,后期不易发生修改,使用链式调用无可厚非,但是如果该链条容易发生变化,导致调试和维护困难,那么普通调用形式为佳。

分解大型类

如果一个类的方法足够复杂和庞大,那么它完全有必要作为一个单独的类存在。面向对象设计鼓励将行为分布在合理数量的更小对象之中。

用return退出多重循环

在函数有两重循环语句时,我们往往需要在内层循环中判断,当达到临界条件时退出外层循环,有以下实现方式。

  1. 设置flag。

var func = function() {
    var flage = false;
    for (var i = 0; i < 10; i++) {
      for (var j = 0; j < 10; j++) {
        if (i * j > 30) {
          flag = true;
          break;
        }
      }
      if (flag === true) {
        break;
      }
    }
}
  1. 设置循环标记

var func = function() {
  outerloop:
  for(var i = 0; i < 10; i++) {
    innerloop:
    for(var j = 0; j < 10; j++) {
      if (i * j >30) {
        break outerloop;
      }
    }
  }
}

这两种做法都让人头晕目眩,更简单的做法是直接终止整个方法:

var func = function() {
    for (var i = 0; i < 10; i++) {
      for (var j = 0; j < 10; j++) {
        if (i * j > 30) {
          return;
        }
      }
    }
}

return直接退出有一个问题,在循环之后如果还有代码就无法执行:

var func = function() {
    for (var i = 0; i < 10; i++) {
      for (var j = 0; j < 10; j++) {
        if (i * j > 30) {
          return;
        }
      }
    }
    console.log(i); // 无法执行
}

我们可以把循环后需要的代码放到return后面,如果代码较多,可以提炼成一个单独的函数。

var print = function(i) {
  console.log(i);
};
var func = function() {
    for (var i = 0; i < 10; i++) {
      for (var j = 0; j < 10; j++) {
        if (i * j > 30) {
          return print(i);
        }
      }
    }
};

发条橙子
399 声望14 粉丝

我爱吃西瓜