提煉函數(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();