如何讓你的 JS 寫得更漂亮

網(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等弄跌,也是可以幫助寫出簡潔高效的代碼甲喝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市铛只,隨后出現(xiàn)的幾起案子埠胖,更是在濱河造成了極大的恐慌,老刑警劉巖淳玩,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件直撤,死亡現(xiàn)場離奇詭異,居然都是意外死亡蜕着,警方通過查閱死者的電腦和手機谋竖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來承匣,“玉大人蓖乘,你說我怎么就攤上這事∪推” “怎么了嘉抒?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長袍暴。 經(jīng)常有香客問我些侍,道長,這世上最難降的妖魔是什么政模? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任岗宣,我火速辦了婚禮,結(jié)果婚禮上览徒,老公的妹妹穿的比我還像新娘狈定。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布纽什。 她就那樣靜靜地躺著措嵌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芦缰。 梳的紋絲不亂的頭發(fā)上企巢,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機與錄音让蕾,去河邊找鬼浪规。 笑死,一個胖子當著我的面吹牛探孝,可吹牛的內(nèi)容都是我干的笋婿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼顿颅,長吁一口氣:“原來是場噩夢啊……” “哼缸濒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粱腻,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤庇配,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绍些,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捞慌,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年柬批,在試婚紗的時候發(fā)現(xiàn)自己被綠了啸澡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡萝快,死狀恐怖锻霎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情揪漩,我是刑警寧澤旋恼,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奄容,受9級特大地震影響冰更,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昂勒,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一蜀细、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戈盈,春花似錦奠衔、人聲如沸谆刨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痊夭。三九已至,卻和暖如春脏里,著一層夾襖步出監(jiān)牢的瞬間她我,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工迫横, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留番舆,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓矾踱,卻偏偏與公主長得像恨狈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子呛讲,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容