1. 程式人生 > 實用技巧 >簡單幾步讓你的 JS 寫得更漂亮

簡單幾步讓你的 JS 寫得更漂亮

網上有不少關於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 "";
else return count * 100;
}

getPrice 這個函式有可能返回一個整數,也有可能返回一個空的字串。這樣寫也不太好,雖然它是符合 JS語法的,但這種編碼風格是不好的。使用你這個函式的人會有點無所適從,不敢直接進行加減乘除,因為如果返回字串進行運算的話值就是 NaN 了。函式的返回型別應該是要確定的,如下面是返回整型:

function getPrice(count){
if(count < 0) return -1;
else return 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 ‘==’. if(a == b){

(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;
else return count * 100;
}

可以改成:

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

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

function getPrice(e){return 0>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);

這樣意義就很明顯了。

品牌vi設計公司http://www.maiqicn.com 辦公資源網站大全https://www.wode007.com

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 要寫這麼多:

function Person(name, age){
this.name = name;
this.age = age;
}

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

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

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

class Person{
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 等,也是可以幫助寫出簡潔高效的程式碼。以上列了我自己在實際寫程式碼過程中遇到的一些問題和一些個人認為比較重要的方面,其它的還有變數命名、縮排、註釋等,這裡就不提及了。寫程式碼的風格也體現了程式設計的素養,有些人的程式碼看起來非常地乾淨利落,而有些人的程式碼看起來讓人比較痛苦。這種程式設計素質的提升需要有意識地去做一些改進,有些人雖然程式碼寫得很爛,但是他自己並不覺得有什麼問題。這就需要多去學下別人的程式碼,甚至學一下其它語言的書寫,兩者一比較就能發現差異,或者看下這方面的書,像什麼程式碼大全之類的。