前端代碼優(yōu)化與重構(gòu)

提煉函數(shù)

這個(gè)方法是我們最經(jīng)常做的優(yōu)化,我們希望在編程過程中,函數(shù)都有良好的命名,而且在函數(shù)的內(nèi)部包含清晰的邏輯,我們?cè)谌粘>幊痰倪^程中,我們往往會(huì)向一個(gè)函數(shù)中塞入大量的代碼,使其違反了單一變量的原則。我們不得不在函數(shù)內(nèi)部加入若干的注釋,讓這個(gè)函數(shù)顯得容易讀懂一些,這個(gè)時(shí)候,我們就需要對(duì)代碼進(jìn)行簡化。我們的做法是:將一些代碼獨(dú)立出,放入到另外一個(gè)單獨(dú)的函數(shù)中。這樣做有如下幾個(gè)優(yōu)點(diǎn):

  • 避免出現(xiàn)超大的函數(shù)
  • 獨(dú)立出來的代碼方便進(jìn)行代碼的復(fù)用
  • 獨(dú)立出來的函數(shù)更加容易被覆寫
  • 獨(dú)立出來的函數(shù)如果擁有一個(gè)良好的命名,它本身就起到了一個(gè)注釋的作用。

我們用一段代碼來舉個(gè)栗子,我們只一個(gè)負(fù)責(zé)獲取用戶信息的函數(shù)里面,我們還需要打印用戶信息相關(guān)的log。我們將打印的語句封裝到一個(gè)獨(dú)立的函數(shù)里面:

//優(yōu)化前
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);
  });
};

//優(yōu)化后
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);
};

合并重復(fù)的條件判斷

如果我們?cè)诤瘮?shù)的內(nèi)部有一些條件分支語句,然后在這些條件分支語句中散布了一些重復(fù)的代碼,那么我們就有必要做一些合并的工作、現(xiàn)在給出一個(gè)應(yīng)用的場景:假設(shè)我們有一個(gè)分頁的函數(shù)paging,這個(gè)函數(shù)接受一個(gè)參數(shù)currPage,currPage表示要跳轉(zhuǎn)的頁碼,在跳轉(zhuǎn)之前要防止currPage過大或是過小,我們要進(jìn)行手動(dòng)的修正??匆欢蝹未a:

//優(yōu)化前
var paging = function(){
  if(currPage <= 0){
    currPage = 0;
    jump(currPage);
  }else if(currPage >= totalPage){
    currPage = totalpage;
    jump(currPage);
  }else{
    jump(currPage);
  }
}

上面的代碼我們可以看到,負(fù)責(zé)跳轉(zhuǎn)的代碼jump(currPage)在每一個(gè)條件分支中都出現(xiàn)了,所以完全可以把這部分的代碼獨(dú)立出來

//優(yōu)化后
var paging = function (currPage){
  if(currPage <= 0){
    currPage = 0;
  }else if (currPage > = totalPage){
    currPage = totalPage;
  }
  jump(currPage);
}

將條件分支語句提煉成函數(shù)

我們?cè)诰幊踢^程中,可能看到別人的代碼有大連的if-else語句,導(dǎo)致最后代碼的可讀性很差。舉一個(gè)生活中的例子:現(xiàn)在有一個(gè)計(jì)算商品的函數(shù),計(jì)算的規(guī)則是,如果當(dāng)前季節(jié)處于夏季,那么全部的商品將以8折出售,代碼如下

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

我們要判斷的表達(dá)的意思是很簡單的,就是判斷當(dāng)前是否為夏季,但是我們?cè)趇f的語句中,很難得到代碼想要表達(dá)的意思。這個(gè)時(shí)候我們可以將這段代碼提煉成一個(gè)單獨(dú)的函數(shù),就可以更加準(zhǔn)確地表達(dá)代碼的意思,函數(shù)本身的函數(shù)名也可以起到注釋的作用。

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

合理的使用循環(huán)

在函數(shù)體的內(nèi)部,會(huì)有很多的重復(fù)性的工作,那么合理的使用循環(huán),就是我們需要考慮的問題,使用循環(huán)可以減少代碼量。加入我們創(chuàng)建XHR對(duì)象的代碼,在這里就不考慮瀏覽器的兼容性了。

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

現(xiàn)在我們對(duì)上面的代碼進(jìn)行優(yōu)化,巧妙的使用循環(huán),達(dá)到和上面代碼一樣的效果:

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

提前讓函數(shù)退出代替嵌套條件分支

我們的編程習(xí)慣讓我們有這樣的觀念:“每一個(gè)函數(shù)只能有一個(gè)出口和入口”?,F(xiàn)代的編程語言都會(huì)限制函數(shù)只有一個(gè)入口,但是對(duì)于函數(shù)只有一個(gè)出口都是有不同的看法。

var del = function(obj){
  var ret;
  if(!obj.isReadyOnly){//文件是只讀模式
    if(obj.isFloder){//是文件夾
      ret = deleteFloder(obj);
    }else if(obj.isFile){//是文件形式
      ret = deleteFile(obj)
    }
  }
  return ret;
};

嵌套的條件分支語句絕對(duì)是代碼維護(hù)者的噩夢,因?yàn)檫壿嬁瓷先ナ值幕靵y,理解上十分的困難,有的時(shí)候外層的if分支在左括號(hào)和有括號(hào)相隔很遠(yuǎn),理解上就更加復(fù)雜了額,我們之前在編程的時(shí)候,一直堅(jiān)信,一個(gè)函數(shù)只有一個(gè)出口,實(shí)際上程序?qū)κO虏糠值倪壿嫴⒉魂P(guān)注,所以這個(gè)時(shí)候可以立即退出,不會(huì)引導(dǎo)程序員去看一些無用的else的語句。

var del = function(obj){
  var ret;
  if(!obj.isReadyOnly){//文件是只讀模式
    return;
   }
  if(obj.isFloder){//是文件夾
    return  deleteFloder(obj);
  }
  if(obj.isFile){//是文件形式
   return deleteFile(obj)
   }
};

傳遞參數(shù)對(duì)象代替過長的參數(shù)列表

有的時(shí)候,一個(gè)函數(shù)可以接受多個(gè)參數(shù),而且參數(shù)的數(shù)量越多,函數(shù)的功能越難理解。因?yàn)槭褂眠@個(gè)函數(shù)的用戶必須知道各個(gè)參數(shù)的作用是什么,使用的時(shí)候,還要小心謹(jǐn)慎,避免多傳或是少傳參數(shù),造成錯(cuò)誤,而且當(dāng)我們想要添加參數(shù)的時(shí)候,設(shè)計(jì)到很多的代碼的修改。

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)

我們可以將參數(shù)放入到一個(gè)對(duì)象里面,然后在將對(duì)象傳入到函數(shù)中,而且不用再傳參的時(shí)候關(guān)心參數(shù)的順序和數(shù)量,只要保證參數(shù)的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
})

盡減少參數(shù)的數(shù)量數(shù)量

如果我們向一個(gè)函數(shù)中傳入很多的參數(shù),那么我們使用的時(shí)候,要先搞懂參數(shù)的意義,這樣很浪費(fèi)時(shí)間。但是在實(shí)際的開發(fā)過程中,向函數(shù)傳入?yún)?shù)是不可避免的,我們假設(shè)一下下面的應(yīng)用場景:有一個(gè)畫圖的函數(shù)draw,他現(xiàn)在只可以繪制長方形,接受了3個(gè)參數(shù)。分別是長寬和面積,但是我們知道,面積是可以通過長和寬計(jì)算出來的。

var draw  = function(width, height,width)

所以我們對(duì)上面的代碼進(jìn)行優(yōu)化,將square參數(shù)從函數(shù)中去掉

var draw  = function(width, height){
  var square = width * height;
}

假設(shè)日后這個(gè)draw函數(shù)支持繪制原型,我們就需要把長和寬的參數(shù)換成半徑radius,但是圖形的面積還是不應(yīng)該由客戶端進(jìn)行傳入,而是應(yīng)該在draw函數(shù)的內(nèi)部,由一定的規(guī)則進(jìn)行計(jì)算。這個(gè)時(shí)候就可以使用策略模式,讓draw函數(shù)支持說中圖形的繪制。

慎用三目運(yùn)算符

有一些程序員喜歡使用三目運(yùn)算符來代替if-else的語句,因?yàn)榇a量少,運(yùn)算性能高。但是三目運(yùn)算符的運(yùn)算性能并不比if-else高很多。而且在負(fù)責(zé)的邏輯中使用三目運(yùn)算符會(huì)降低代碼的可讀性和可維護(hù)性。而且讓js文件加載的速度加快的方法有很多。比如說壓縮、緩存等等,但是僅僅把關(guān)注點(diǎn)放在使用三目運(yùn)算符的數(shù)目上,無異于將一個(gè)300斤超重的胖子的超重原因歸結(jié)到了頭皮屑上面。
什么時(shí)候使用三目運(yùn)算符:當(dāng)條件分支的邏輯十分的簡單清楚。

var global = typeof window !== 'undefined'? window:this;

但是如果我們的條件十分的復(fù)雜,我們還是按部就班的寫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
}

合理的使用鏈?zhǔn)秸{(diào)用

經(jīng)常使用jquery的程序員比較習(xí)慣使用鏈?zhǔn)秸{(diào)用的寫法,在JavaScript中,可以很容易的實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,即讓方法調(diào)用結(jié)束后返回對(duì)象自身。

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

通常來說,臉是調(diào)用的方式并不會(huì)造成閱讀理解時(shí)候的困難,也可以減少一些中間變量,但是節(jié)省下來的字節(jié)幾乎可以忽略布局。鏈?zhǔn)秸{(diào)用帶來的壞處就是調(diào)試的時(shí)候十分的不方便,只要中間一節(jié)出現(xiàn)了錯(cuò)誤,必須將整條鏈拆開,再添加斷點(diǎn),才可以定位錯(cuò)誤出現(xiàn)的位置。

用return退出多重循環(huán)

假設(shè)函數(shù)體內(nèi)有一個(gè)兩重的循環(huán)語句,我們需要內(nèi)層循環(huán)中判斷,當(dāng)?shù)竭_(dá)某個(gè)臨界條件時(shí)退出外層的循環(huán)。我們大多數(shù)時(shí)候會(huì)引入一個(gè)控制標(biāo)記變量:

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

第二種寫法是設(shè)置循環(huán)標(biāo)記:

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

這兩種做法都沒有錯(cuò),但是還有更加簡單的做法,就是在終止循環(huán)的時(shí)候。直接的退出整個(gè)方法:

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

當(dāng)然直接這么寫又會(huì)帶來一個(gè)新的問題,就是如果在循環(huán)后還有一些將要被執(zhí)行的代碼,直接退出整個(gè)方法,這些方法就不會(huì)有被執(zhí)行的機(jī)會(huì)了,舉個(gè)栗子:

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

為了我們解決這個(gè)問題,我們將循環(huán)后的戴拿直接放在return的后面,如果代碼的比較多,就直接將其提煉成一個(gè)函數(shù)。

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();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,569評(píng)論 0 13
  • 截圖 打開模擬器 選擇需要截屏的頁面 按Command+S鍵進(jìn)行截屏,截屏文件一般存儲(chǔ)在當(dāng)前桌面 錄屏 打開模擬器...
    ManThirty閱讀 1,694評(píng)論 0 0
  • chaocre閱讀 179評(píng)論 0 0
  • 春天的陽光要把我曬裂 讓我心中的種子,在縫隙中生長 你遠(yuǎn)遠(yuǎn)地看著我 在夜深人靜的時(shí)候借助風(fēng) 送我一點(diǎn)滋潤的雨露 因...
    泰安左眼皮跳跳閱讀 227評(píng)論 0 8
  • 你說人家寫出來都是高山和流水 而我只有下三路和姑娘的乳房 你說在這樣的夜里 人家喜歡端著茶思考人生 而我永遠(yuǎn)都只有...
    娃哈哈烏拉拉閱讀 175評(píng)論 0 1

友情鏈接更多精彩內(nèi)容