網(wǎng)上有不少關(guān)于JS編寫優(yōu)化建議傻昙,這里我根據(jù)自己的經(jīng)驗提出一些比較有用的意見。
1. 按強類型風格寫代碼
JS是弱類型的,但是寫代碼的時候不能太隨意险污,寫得太隨意也體現(xiàn)了編碼風格不好。下面分點說明:
(1)定義變量的時候要指明類型富岳,告訴JS解釋器這個變量是什么數(shù)據(jù)類型的蛔糯,而不要讓解釋器去猜,例如不好的寫法:
var num,
str,
obj;
聲明了三個變量窖式,但其實沒什么用蚁飒,因為解釋器不知道它們是什么類型的,好的寫法應(yīng)該是這樣的:
var num = 0,
str = '',
obj = null;
定義變量的時候就給他一個默認值萝喘,這樣不僅方便了解釋器淮逻,也方便了閱讀代碼的人,他會在心里有數(shù)——知道這些變量可能會當作什么用阁簸。
(2)不要隨意地改變變量的類型弦蹂,例如下面代碼:
var num = 5;
num = "-" + num;
第1行它是一個整型,第2行它變成了一個字符串强窖。因為JS最終都會被解釋成匯編的語言,匯編語言變量的類型肯定是要確定的削祈,你把一個整型的改成了字符串翅溺,那解釋器就得做一些額外的處理脑漫。并且這種編碼風格是不提倡的,有一個變量第1行是一個整型咙崎,第10行變成了一個字符串优幸,第20行又變成了一個object,這樣就讓閱讀代碼的人比較困惑褪猛,上面明明是一個整數(shù)网杆,怎么突然又變成一個字符串了。好的寫法應(yīng)該是再定義一個字符串的變量:
var num = 5;
var sign = "-" + num;
(3)函數(shù)的返回類型應(yīng)該是要確定的伊滋,例如下面不確定的寫法:
function getPrice(count){
if(count < 0) return "";
else return count * 100;
}
getPrice這個函數(shù)有可能返回一個整數(shù)碳却,也有可能返回一個空的字符串。這樣寫也不太好笑旺,雖然它是符合JS語法的昼浦,但這種編碼風格是不好的。使用你這個函數(shù)的人會有點無所適從筒主,不敢直接進行加減乘除关噪,因為如果返回字符串進行運算的話值就是NaN了。函數(shù)的返回類型應(yīng)該是要確定的乌妙,如下面是返回整型:
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變量,第二行在使用的時候焕盟,需要在全局作用域查找一下這個變量秋秤,假設(shè)map是在一個循環(huán)里面使用,那可能就會有效率問題了脚翘。所以應(yīng)該要把它搞成一個局部的作用域:
<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函數(shù)拍嵌,在這個函數(shù)里面count變量的查找時間要高于局部的factor變量。其實這里不太適合用閉包告抄,可以直接把count傳給process:
function getResult(count){
count++;
function process(count){
var factor = 2;
return count * factor - 5;
}
return process(count);
}
這樣count的查找時間就和factor一樣撰茎,都是在當前作用域直接命中。這個就啟示我們?nèi)绻硞€全局變量需要頻繁地被使用的時候打洼,可以用一個局部變量緩存一下龄糊,如下:
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){
}
上面的兩個例子都是確定類型的瓮下,一個是字符串,一個是整數(shù)钝域。就沒必要使用==了讽坏,直接用===就可以了。
(2)如果類型不確定例证,那么應(yīng)該手動做一下類型轉(zhuǎn)換路呜,而不是讓別人或者以后的你去猜這里面有類型轉(zhuǎn)換,如下:
var totalPage = "5";
if(parseInt(totalPage) === 1){
}
(3)使用==在JSLint檢查的時候是不通過的:
if(a == b){
}
如下JSLint的輸出:
Expected ‘===’ and instead saw ‘==’.
if(a == b){
(4)并且使用==可能會出現(xiàn)一些奇怪的現(xiàn)象织咧,這些奇怪的現(xiàn)象可能會給代碼埋入隱患:
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是兩個毫無關(guān)系的值,null應(yīng)該是作為初始化空值使用手趣,而undefined是用于檢驗?zāi)硞€變量是否未定義晌该。
這和第1點介紹強類型的思想是相通的。
4. 合并表達式
如果用1句代碼就可以實現(xiàn)5句代碼的功能绿渣,那往往1句代碼的執(zhí)行效率會比較高朝群,并且可讀性可能會更好
(1)用三目運算符取代簡單的if-else
如上面的getPrice函數(shù):
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)連等
連等是利用賦值運算表達式會返回所賦的值,并且執(zhí)行順序是從右到左的淀散,如下:
overtime = favhouse = listingDetail = {...}
有時候你會看到有人這樣寫:
var age = 0;
if((age = +form.age.value) >= 18){
console.log("你是成年人");
} else {
consoe.log("小朋友右莱,你還有" + (18 - age) + "就成年了");
}
也是利用了賦值表達式會返回一個值,在if里面賦值的同時用它的返回值做判斷档插,然后else里面就已經(jīng)有值了慢蜓。上面的+號把字符串轉(zhuǎn)成了整數(shù)。
(3)自增
利用自增也可以簡化代碼郭膛。如下晨抡,每發(fā)出一條消息,localMsgId就自增1:
chatService.sendMessage(localMsgId++, msgContent);
5. 減少魔數(shù)
例如则剃,在某個文件的第800行耘柱,冒出來了一句:
dialogHandler.showQuestionNaire("seller", "sell", 5, true);
就會讓人很困惑了,上面的四個常量分別代表什么呢棍现,如果我不去查一個那個函數(shù)的變量說明就不能夠很快地意會到這些常量分別有什么用调煎。這些意義不明的常量就叫“魔數(shù)”。
所以最好還是給這些常量取一個名字轴咱,特別是在一些比較關(guān)鍵的地方汛蝙。例如上面的代碼可改成:
var naireType = "seller",
dialogType = "sell",
questionsCount = 5,
reloadWindow = true;
naireHandler.showNaire(naireType, dialogType, questionsCount, reloadWindow);
這樣意義就很明顯了。
6. 使用ES6簡化代碼
ES6已經(jīng)發(fā)展很多年了朴肺,兼容性也已經(jīng)很好了窖剑。恰當?shù)厥褂茫梢宰尨a更加地簡潔優(yōu)雅戈稿。
(1)使用箭頭函數(shù)取代小函數(shù)
有很多使用小函數(shù)的場景西土,如果寫個function,代碼起碼得寫3行鞍盗,但是用箭頭函數(shù)一行就搞定了需了,例如實現(xiàn)數(shù)組從大到小排序:
var nums = [4, 8, 1, 9, 0];
nums.sort(function(a, b){
return b - a;
});
//輸出[9, 8, 4, 1, 0]
如果用箭頭函數(shù)跳昼,排序只要一行就搞定了:
var nums = [4, 8, 1, 9, 0];
nums.sort(a, b => b - a);
代碼看起來簡潔多了,還有setTimeout里面經(jīng)常會遇到只要執(zhí)行一行代碼就好了肋乍,寫個function總感覺有點麻煩鹅颊,用字符串的方式又不太好,所以這種情況用箭頭函數(shù)也很方便:
setTimeout(() => console.log("hi"), 3000)
箭頭函數(shù)在C++/Java等其它語言里面叫做Lambda表達式墓造,Ruby比較早就有這種語法形式了堪伍,后來C++/Java也實現(xiàn)了這種語法。
當然箭頭函數(shù)或者Lambda表達式不僅適用于這種一行的觅闽,多行代碼也可以缝驳,不過在一行的時候它的優(yōu)點才比較明顯甜害。
(2)使用ES6的class
雖然ES6的class和使用function的prototype本質(zhì)上是一樣的湾碎,都是用的原型觉增。但是用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還可以很方便地實現(xiàn)繼承吮廉、靜態(tài)的成員函數(shù),就不需要自己再去通過一些技巧去實現(xiàn)了硫惕。
(3)字符串拼接
以前要用+號拼接:
var tpl =
'<div>' +
' <span>1</span>' +
'</div>';
現(xiàn)在只要用兩個反引號“`”就可以了:
var tpl =
` <div>
<span>1</span>
</div>
`;
另外反引號還支持占位替換茧痕,原本你需要:
var page = 5,
type = encodeURIComponet("#js");
var url = "/list?page=" + page + "&type=" + type;
現(xiàn)在只需要:
var url = `/list?page=${page}&type=${type}`;
就不用使用+號把字符串拆散了。
(4)塊級作用域變量
塊級作用域變量也是ES6的一個特色恼除,下面的代碼是一個任務(wù)隊列的模型抽象:
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]();
}
但是上面代碼的執(zhí)行輸出是4踪旷,4,4豁辉,4令野,并且不是想要輸出:0,1徽级,2气破,3,所以每個task就不能取到它的index了餐抢,這是因為閉包都是用的同一個i變量现使,i已經(jīng)變成4了,所以執(zhí)行閉包的時候就都是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的一個參數(shù)欺抗,每次執(zhí)行函數(shù)的時候售碳,肯定會實例化新的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循環(huán)里面有個大括號倘要,大括號就是一個獨立的作用域,let定義的變量在獨立的作用域里面它的值也是獨立的力惯。當然即使沒寫大括號for循環(huán)執(zhí)行也是獨立的碗誉。
除了以上幾點,ES6還有其它一些比較好用的功能父晶,如Object的assign,Promise等弄跌,也是可以幫助寫出簡潔高效的代碼甲喝。