这是《 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退出多重循环
在函数有两重循环语句时,我们往往需要在内层循环中判断,当达到临界条件时退出外层循环,有以下实现方式。
设置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;
}
}
}
设置循环标记
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);
}
}
}
};