前端代码优化与重构
提炼函数
这个方法是我们最经常做的优化,我们希望在编程过程中,函数都有良好的命名,而且在函数的内部包含清晰的逻辑,我们在日常编程的过程中,我们往往会向一个函数中塞入大量的代码,使其违反了单一变量的原则。我们不得不在函数内部加入若干的注释,让这个函数显得容易读懂一些,这个时候,我们就需要对代码进行简化。我们的做法是:将一些代码独立出,放入到另外一个单独的函数中。这样做有如下几个优点:
- 避免出现超大的函数
- 独立出来的代码方便进行代码的复用
- 独立出来的函数更加容易被覆写
- 独立出来的函数如果拥有一个良好的命名,它本身就起到了一个注释的作用。
我们用一段代码来举个栗子,我们只一个负责获取用户信息的函数里面,我们还需要打印用户信息相关的log。我们将打印的语句封装到一个独立的函数里面:
//优化前
var getUserInfo = function(){
ajax('http://XXX.com/userInfo', function( data){
console.log('userId:' + data.userId);
console.log('userName:' + data.userName);
console.log('nickName:' + data.nickName);
});
};
//优化后
var getUserInfo = function(){
ajax('http://XXX.com/userInfo', function( data){
printDetail(data);
});
};
var printDetail = function(data){
console.log('userId:' + data.userId);
console.log('userName:' + data.userName);
console.log('nickName:' + data.nickName);
};
合并重复的条件判断
如果我们在函数的内部有一些条件分支语句,然后在这些条件分支语句中散布了一些重复的代码,那么我们就有必要做一些合并的工作、现在给出一个应用的场景:假设我们有一个分页的函数paging
,这个函数接受一个参数currPage
,currPage
表示要跳转的页码,在跳转之前要防止currPage
过大或是过小,我们要进行手动的修正。看一段伪代码:
//优化前
var paging = function(){
if(currPage <= 0){
currPage = 0;
jump(currPage);
}else if(currPage >= totalPage){
currPage = totalpage;
jump(currPage);
}else{
jump(currPage);
}
}
上面的代码我们可以看到,负责跳转的代码jump(currPage)
在每一个条件分支中都出现了,所以完全可以把这部分的代码独立出来
//优化后
var paging = function (currPage){
if(currPage <= 0){
currPage = 0;
}else if (currPage > = totalPage){
currPage = totalPage;
}
jump(currPage);
}
将条件分支语句提炼成函数
我们在编程过程中,可能看到别人的代码有大连的if-else
语句,导致最后代码的可读性很差。举一个生活中的例子:现在有一个计算商品的函数,计算的规则是,如果当前季节处于夏季,那么全部的商品将以8折出售,代码如下
var getPrice = function(price){
var date = new Date();
if(date.getMonth() >= 6 && date,getMonth() <= 9){
return price * 0.8
}
};
我们要判断的表达的意思是很简单的,就是判断当前是否为夏季,但是我们在if的语句中,很难得到代码想要表达的意思。这个时候我们可以将这段代码提炼成一个单独的函数,就可以更加准确地表达代码的意思,函数本身的函数名也可以起到注释的作用。
var isSummer = function(){
var date = new Date();
return date.getMonth()>6 && date.getMonth() < 9
}
var getPrice = function(price){
var date = new Date();
if(isSummer()){
return price * 0.8
}
};
合理的使用循环
在函数体的内部,会有很多的重复性的工作,那么合理的使用循环,就是我们需要考虑的问题,使用循环可以减少代码量。加入我们创建XHR
对象的代码,在这里就不考虑浏览器的兼容性了。
var createXHR = function(){
var xhr;
try{
xhr = new ActiveXObject('MSXML2.XMLHTTP.6.0');
}cache(e){
try{
xhr = new ActiveXObject('MSXML2.XMLHTTP.3.0');
}cache(e){
xhr = new ActiveXObject('MSXML2.XMLHTTP');
}
}
return xhr;
}
var xhr = createXHR ();
现在我们对上面的代码进行优化,巧妙的使用循环,达到和上面代码一样的效果:
var createXHR = function(){
var versions = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
for(let i = 0,version; version = versions[i++]){
try{
return new ActiveXObject(version);
}cache(e){
}
}
};
var xhr = createXHR ();
提前让函数退出代替嵌套条件分支
我们的编程习惯让我们有这样的观念:“每一个函数只能有一个出口和入口”。现代的编程语言都会限制函数只有一个入口,但是对于函数只有一个出口都是有不同的看法。
var del = function(obj){
var ret;
if(!obj.isReadyOnly){//文件是只读模式
if(obj.isFloder){//是文件夹
ret = deleteFloder(obj);
}else if(obj.isFile){//是文件形式
ret = deleteFile(obj)
}
}
return ret;
};
嵌套的条件分支语句绝对是代码维护者的噩梦,因为逻辑看上去十分的混乱,理解上十分的困难,有的时候外层的if分支在左括号和有括号相隔很远,理解上就更加复杂了额,我们之前在编程的时候,一直坚信,一个函数只有一个出口,实际上程序对剩下部分的逻辑并不关注,所以这个时候可以立即退出,不会引导程序员去看一些无用的else
的语句。
var del = function(obj){
var ret;
if(!obj.isReadyOnly){//文件是只读模式
return;
}
if(obj.isFloder){//是文件夹
return deleteFloder(obj);
}
if(obj.isFile){//是文件形式
return deleteFile(obj)
}
};
传递参数对象代替过长的参数列表
有的时候,一个函数可以接受多个参数,而且参数的数量越多,函数的功能越难理解。因为使用这个函数的用户必须知道各个参数的作用是什么,使用的时候,还要小心谨慎,避免多传或是少传参数,造成错误,而且当我们想要添加参数的时候,设计到很多的代码的修改。
var setUserInfo = function(id,name, address, sex, mobile,qq){
console.log('id:'+id);
console.log('name:'+name);
console.log('address:'+address);
console.log('sex:'+sex);
console.log('mobile:'+mobile);
console.log('qq:'+qq);
}
setUserInfo (1212, 'kim','shanghai','female','123456788912',12345678978)
我们可以将参数放入到一个对象里面,然后在将对象传入到函数中,而且不用再传参的时候关心参数的顺序和数量,只要保证参数的key
值不变就可以了。
var setUserInfo = function(obj){
console.log('id:'+id);
console.log('name:'+name);
console.log('address:'+address);
console.log('sex:'+sex);
console.log('mobile:'+mobile);
console.log('qq:'+qq);
}
setUserInfo ({
id:1212,
name:'kim',
address:'shanghai',
sex:'female',
mobile:'123456788912',
qq:12345678978
})
尽减少参数的数量数量
如果我们向一个函数中传入很多的参数,那么我们使用的时候,要先搞懂参数的意义,这样很浪费时间。但是在实际的开发过程中,向函数传入参数是不可避免的,我们假设一下下面的应用场景:有一个画图的函数draw
,他现在只可以绘制长方形,接受了3个参数。分别是长宽和面积,但是我们知道,面积是可以通过长和宽计算出来的。
var draw = function(width, height,width)
所以我们对上面的代码进行优化,将square参数从函数中去掉
var draw = function(width, height){
var square = width * height;
}
假设日后这个draw
函数支持绘制原型,我们就需要把长和宽的参数换成半径radius
,但是图形的面积还是不应该由客户端进行传入,而是应该在draw
函数的内部,由一定的规则进行计算。这个时候就可以使用策略模式,让draw
函数支持说中图形的绘制。
慎用三目运算符
有一些程序员喜欢使用三目运算符来代替if-else
的语句,因为代码量少,运算性能高。但是三目运算符的运算性能并不比if-else
高很多。而且在负责的逻辑中使用三目运算符会降低代码的可读性和可维护性。而且让js文件加载的速度加快的方法有很多。比如说压缩、缓存等等,但是仅仅把关注点放在使用三目运算符的数目上,无异于将一个300斤超重的胖子的超重原因归结到了头皮屑上面。
什么时候使用三目运算符:当条件分支的逻辑十分的简单清楚。
var global = typeof window !== 'undefined'? window:this;
但是如果我们的条件十分的复杂,我们还是按部就班的写if语句比较好
if(!aup || bup){
return a === doc ? -1:
b=== doc ? 1:
aup ? -1:
bup ? 1:
sortInput ?
(indexOf.call (sortInput,a) - indexOf.call(sortInput,b)):
0
}
合理的使用链式调用
经常使用jquery的程序员比较习惯使用链式调用的写法,在JavaScript
中,可以很容易的实现链式调用,即让方法调用结束后返回对象自身。
var User = function (){
this.id = null;
this.name = null;
};
User.prototype.setId = function (id){
this.id = id;
return this;
}
User.prototype.setName = function (name){
this.name= name;
return this;
}
console.log(new User().setId(1212).setName('kim'));
//写法二
var User = {
id : null,
name: null,
setId: function(id){
this.id = id;
return this;
}
setId: function(name){
this.name = name;
return this;
}
};
console.log(User.setId(1212).setName('kim'));
通常来说,脸是调用的方式并不会造成阅读理解时候的困难,也可以减少一些中间变量,但是节省下来的字节几乎可以忽略布局。链式调用带来的坏处就是调试的时候十分的不方便,只要中间一节出现了错误,必须将整条链拆开,再添加断点,才可以定位错误出现的位置。
用return退出多重循环
假设函数体内有一个两重的循环语句,我们需要内层循环中判断,当到达某个临界条件时退出外层的循环。我们大多数时候会引入一个控制标记变量:
var func = function(){
var flag = false;
for(let i = 0; i< 10 ; i++){
for(let j = 0; j < 10; j++){
if(i *j > 30){
flag = true;
break;
}
}
if(flag === true){
break;
}
}
};
第二种写法是设置循环标记:
var func = function(){
outerloop:
for(let i = 0; i< 10; i++){
innerloop:
for(let j = 0; j<10; j++){
if(i * j >30){
break outerloop;
}
}
}
}
这两种做法都没有错,但是还有更加简单的做法,就是在终止循环的时候。直接的退出整个方法:
var func = function(){
for(let i = 0; i <10;i++){
for (let j = 0;j <10; j++){
if(i *j >30){
return;
}
}
}
};
当然直接这么写又会带来一个新的问题,就是如果在循环后还有一些将要被执行的代码,直接退出整个方法,这些方法就不会有被执行的机会了,举个栗子:
var func = function(){
for(let i = 0; i <10;i++){
for (let 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(let i = 0; i <10;i++){
for (let j = 0;j <10; j++){
if(i *j >30){
return print (i);
}
}
}
};
func();