天天看點

6個幫助你寫出漂亮JavaScript代碼的實用技巧

6個幫助你寫出漂亮JavaScript代碼的實用技巧

我覺得寫好代碼和作文章差不多,無外乎:工整、優雅、拒絕重複、惜字如金。對代碼有感情,每一行都應該盡心盡力,并且還要有把所有代碼扔垃圾簍之後再重寫兩遍的沖動,一旦有了這種沖動之後,什麼都擋不住你,連吃喝拉撒時,問題都會浮現到你腦子裡,你就會不由自主地解決它們……

網上有不少關于 js 編寫優化建議,這裡我根據自己的經驗提出一些比較有用的建議。

1、按強類型風格寫代碼

js是弱類型的,但是寫代碼的時候不能太随意,寫得太随意也展現了編碼風格不好。下面分點說明: 

(1)定義變量的時候要指明類型,告訴JS解釋器這個變量是什麼資料類型的,而不要讓解釋器去猜,例如不好的寫法:

var num,
   str,
   obj;      

聲明了三個變量,但其實沒什麼用,因為解釋器不知道它們是什麼類型的,好的寫法應該是這樣的:

var num =0,
   str ='',
   obj =null;      

定義變量的時候就給他一個預設值,這樣不僅友善了解釋器,也友善了閱讀代碼的人,他會在心裡有數——知道這些變量可能會當作什麼用。

(2)不要随意地改變變量的類型,例如下面代碼:

var num =5;
num ="-"+ num;      

第1行它是一個整型,第2行它變成了一個字元串。因為JS最終都會被解釋成彙編的語言,彙編語言變量的類型肯定是要确定的,你把一個整型的改成了字元串,那解釋器就得做一些額外的處理。

并且這種編碼風格是不提倡的,有一個變量第1行是一個整型,第10行變成了一個字元串,第20行又變成了一個object,這樣就讓閱讀代碼的人比較困惑,上面明明是一個整數,怎麼突然又變成一個字元串了。好的寫法應該是再定義一個字元串的變量:

var num =5;
var sign ="-"+ num;      

(3)函數的傳回類型應該是要确定的,例如下面不确定的寫法:

function getPrice(count){
   if(count <0)return"";
   elsereturn count *100;
}      

getPrice這個函數有可能傳回一個整數,也有可能傳回一個空的字元串。這樣寫也不太好,雖然它是符合JS文法的,但這種編碼風格是不好的。

使用你這個函數的人會有點無所适從,不敢直接進行加減乘除,因為如果傳回字元串進行運算的話值就是NaN了。函數的傳回類型應該是要确定的,如下面是傳回整型:

function getPrice(count){
   if(count <0)return-1;
   elsereturn count *100;
}      

然後告訴使用者,如果傳回-1就表示不合法。如果類型确定,解釋器也不用去做一些額外的工作,可以加快運作速度。

2、減少作用域查找

(1)不要讓代碼暴露在全局作用域下 

例如以下運作在全局作用域的代碼:

<script>
   var map = document.querySelector("#my-map");
   map.style.height ="600px";
</script>      

有時候你需要在頁面直接寫一個script,要注意在一個script标簽裡面,代碼的上下文都是全局作用域的,由于全局作用域比較複雜,查找比較慢。

例如上面的map變量,第二行在使用的時候,需要在全局作用域查找一下這個變量,假設map是在一個循環裡面使用,那可能就會有效率問題了。

是以應該要把它搞成一個局部的作用域:

<script>
!function(){
   var map = document.querySelector("#my-map");
   map.style.height ="600px";
}()
</script>      

上面用了一個function制造一個局部作用域,也可以用ES6的塊級作用域。

由于map這個變量直接在目前的局部作用域命中了,是以就不用再往上一級的作用域(這裡是全局作用域)查找了,而局部作用域的查找是很快的。同時直接在全局作用域定義變量,會污染window對象。

(2)不要濫用閉包 

閉包的作用在于可以讓子級作用域使用它父級作用域的變量,同時這些變量在不同的閉包是不可見的。

這樣就導緻了在查找某個變量的時候,如果目前作用域找不到,就得往它的父級作用域查找,一級一級地往上直到找到了,或者到了全局作用域還沒找到。

是以如果閉包嵌套得越深,那麼變量查找的時間就越長。如下:

function getResult(count){
   count++;
   function process(){
       var factor =2;
       return count * factor -5;
   }
   return process();
}      

上面的代碼定義了一個process函數,在這個函數裡面count變量的查找時間要高于局部的factor變量。其實這裡不太适合用閉包,可以直接把count傳給process:

function getResult(count){
   count++;
   function process(count){
       var factor =2;
       return count * factor -5;
   }
   return process(count);
}      

這樣count的查找時間就和factor一樣,都是在目前作用域直接命中。

這個就啟示我們如果某個全局變量需要頻繁地被使用的時候,可以用一個局部變量緩存一下,如下:

var url ="";
if(window.location.protocal ==="https:"){
   url ="wss://xxx.com"+ window.location.pathname + window.location.search;
}      

頻繁地使用了window.location對象,是以可以先把它緩存一下:

var url ="";
var location = window.location;
if(location.protocal ==="https:"){
   url ="wss://xxx.com"+ location.pathname + location.search;
}      

搞成了一個局變變量,這樣查找就會明顯快于全局的查找,代碼也可以寫少一點。

3、避免==的使用

這裡你可能會有疑問了,有些人喜歡用==,有些人喜歡用===,大家的風格不一樣,你為什麼要強制别人用===呢?習慣用==的人,不能僅僅是因為==比===少敲了一次鍵盤。為什麼不提倡用==呢?

(1)如果你确定了變量的類型,那麼就沒必要使用==了,如下:

if(typeof num !="undefined"){




}
var num = parseInt(value);
if(num ==10){




}      

上面的兩個例子都是确定類型的,一個是字元串,一個是整數。就沒必要使用==了,直接用===就可以了。

(2)如果類型不确定,那麼應該手動做一下類型轉換,而不是讓别人或者以後的你去猜這裡面有類型轉換,如下:

var totalPage ="5";
if(parseInt(totalPage)===1){




}      

(3)使用==在JSLint檢查的時候是不通過的:

if(a == b){




}      

如下JSLint的輸出:

Expected ‘===’ and instead saw ‘==’.

(4)并且使用==可能會出現一些奇怪的現象,這些奇怪的現象可能會給代碼埋入隐患:

null==undefined          //true


'' == '0'                  //false


0  == ''                   //true


0  == '0'                  //true


'
 ' == 0            //true


new String("abc") == "abc" //true


new Boolean(true) == true  //true


true == 1                  //true      

上面的比較在用===的時候都是false,這樣才是比較合理的。例如第一點null居然會等于undefined,就特别地奇怪,因為null和undefined是兩個毫無關系的值,null應該是作為初始化空值使用,而undefined是用于檢驗某個變量是否未定義。

這和第1點介紹強類型的思想是相通的。

4、 合并表達式

如果用1句代碼就可以實作5句代碼的功能,那往往1句代碼的執行效率會比較高,并且可讀性可能會更好

(1)用三目運算符取代簡單的if-else 

如上面的getPrice函數:

function getPrice(count){
   if(count <0)return-1;
   elsereturn count *100;
}      

可以改成:

function getPrice(count){
   return count <0?return-1: count *100;
}      

這個比寫一個if-else看起來清爽多了。當然,如果你寫了if-else,壓縮工具也會幫你把它改三目運算符的形式:

function getPrice(e){return0>e?-1:100*e}      

(2)連等 

連等是利用指派運算表達式會傳回所賦的值,并且執行順序是從右到左的,如下:​

overtime = favhouse = listingDetail ={...}      

有時候你會看到有人這樣寫:

var age =0;
if((age =+form.age.value)>=18){
   console.log("你是成年人");
}else{
   consoe.log("小朋友,你還有"+(18- age)+"就成年了");
}      

也是利用了指派表達式會傳回一個值,在if裡面指派的同時用它的傳回值做判斷,然後else裡面就已經有值了。上面的+号把字元串轉成了整數。

(3)自增 

利用自增也可以簡化代碼。如下,每發出一條消息,localMsgId就自增1:

chatService.sendMessage(localMsgId++, msgContent);      

5、減少魔數

例如,在某個檔案的第800行,冒出來了一句:

dialogHandler.showQuestionNaire("seller","sell",5,true);      

就會讓人很困惑了,上面的四個常量分别代表什麼呢,如果我不去查一個那個函數的變量說明就不能夠很快地意會到這些常量分别有什麼用。這些意義不明的常量就叫“魔數”。

是以最好還是給這些常量取一個名字,特别是在一些比較關鍵的地方。例如上面的代碼可改成:

var naireType ="seller",
   dialogType ="sell",
   questionsCount =5,
   reloadWindow =true;
naireHandler.showNaire(naireType, dialogType, questionsCount, reloadWindow);      

這樣意義就很明顯了。

6、使用ES6簡化代碼

ES6已經發展很多年了,相容性也已經很好了。恰當地使用,可以讓代碼更加地簡潔優雅。

(1)使用箭頭函數取代小函數 

有很多使用小函數的場景,如果寫個function,代碼起碼得寫3行,但是用箭頭函數一行就搞定了,例如實作數組從大到小排序:

var nums =[4,8,1,9,0];
nums.sort(function(a, b){
   return b - a;
});
//輸出[9, 8, 4, 1, 0]      

如果用箭頭函數,排序隻要一行就搞定了:

var nums =[4,8,1,9,0];
nums.sort(a, b => b - a);      

代碼看起來簡潔多了,還有setTimeout裡面經常會遇到隻要執行一行代碼就好了,寫個function總感覺有點麻煩,用字元串的方式又不太好,是以這種情況用箭頭函數也很友善:​

setTimeout(()=> console.log("hi"),3000)      

箭頭函數在C++/Java等其它語言裡面叫做Lambda表達式,Ruby比較早就有這種文法形式了,後來C++/Java也實作了這種文法。

當然箭頭函數或者Lambda表達式不僅适用于這種一行的,多行代碼也可以,不過在一行的時候它的優點才比較明顯。

(2)使用ES6的class 

雖然ES6的class和使用function的prototype本質上是一樣的,都是用的原型。但是用class可以減少代碼量,同時讓代碼看起來更加地高大上,使用function要寫這麼多:

functionPerson(name, age){
   this.name = name;
   this.age = age;
}




Person.prototype.addAge =function(){
   this.age++;
};




Person.prototype.setName =function(name){
   this.name = name;
};      

使用class代碼看加地簡潔易懂:

classPerson{
   constructor(name, age){
       this.name = name;
       this.age = age;
   }
   addAge(){
       this.age++;
   }
   setName(name){
       this.name = name;
   }
}      

并且class還可以很友善地實作繼承、靜态的成員函數,就不需要自己再去通過一些技巧去實作了。

(3)字元串拼接 

以前要用+号拼接:

var tpl =
   '<div>'+
   '    <span>1</span>'+
   '</div>';      

現在隻要用兩個反引号“`”就可以了:

var tpl =
`   <div>
       <span>1</span>
   </div>
`;      

另外反引号還支援占位替換,原本你需要:

var page =5,
   type = encodeURIComponet("#js");
var url ="/list?page="+ page +"&type="+ type;      

現在隻需要:

var url =`/list?page=${page}&type=${type}`;      

就不用使用+号把字元串拆散了。

(4)塊級作用域變量 

塊級作用域變量也是ES6的一個特色,下面的代碼是一個任務隊列的模型抽象:

var tasks =[];
for(var i =0; i <4; i++){
   tasks.push(function(){
       console.log("i is "+ i);
   });
}
for(var j =0; j < tasks.length; j++){
   tasks[j]();
}      

但是上面代碼的執行輸出是4,4,4,4,并且不是想要輸出:0,1,2,3,是以每個task就不能取到它的index了,這是因為閉包都是用的同一個i變量,i已經變成4了,是以執行閉包的時候就都是4了。

那怎麼辦呢?可以這樣解決:

var tasks =[];
for(var i =0; i <4; i++){
   !function(k){
       tasks.push(function(){
           console.log("i is "+ k);
       });
   }(i);
}
for(var j =0; j < tasks.length; j++){
   tasks[j]();
}      

把i指派給了k,由于k它是一個function的一個參數,每次執行函數的時候,肯定會執行個體化新的k,是以每次的k都是不同的變量,這樣就輸出就正常了。

但是代碼看起來有點别扭,如果用ES6,隻要把var改成let就可以了:

var tasks =[];
for(let i =0; i <=4; i++){
   tasks.push(function(){
       console.log("i is "+ i);
   });
}
for(var j =0; j < tasks.length; j++){
   tasks[j]();
}      

隻改動了3個字元就達到了目的。因為for循環裡面有個大括号,大括号就是一個獨立的作用域,let定義的變量在獨立的作用域裡面它的值也是獨立的。

當然即使沒寫大括号for循環執行也是獨立的。

除了以上幾點,ES6還有其它一些比較好用的功能,如Object的assign,Promise等,也是可以幫助寫出簡潔高效的代碼。

總結

以上列了我自己在實際寫代碼過程中遇到的一些問題和一些個人認為比較重要的方面,其它的還有變量命名、縮進、注釋等,這裡就不提及了。

寫代碼的風格也展現了程式設計的素養,有些人的代碼看起來非常地幹淨利落,而有些人的代碼看起來讓人比較痛苦。

這種程式設計素質的提升需要有意識地去做一些改進,有些人雖然代碼寫得很爛,但是他自己并不覺得有什麼問題。

這就需要多去學下别人的代碼,甚至學一下其它語言的書寫,兩者一比較就能發現差異,或者看下這方面的書,像什麼代碼大全之類的。

學習更多技能

請點選下方公衆号