深入理解JavaScript系列(1)

2018-06-09 15:51 更新

本文是深入理解JavaScript系列的第篇讀文筆記,博客原文在這里。

內(nèi)容簡要

本文是湯姆大叔《JavaScript Patterns》的基礎(chǔ)上,可能參考了一些其他的文章,寫成的一篇最佳實踐Style文章。全文緊扣如何編寫高質(zhì)量JavaScript代碼這一問題,通過羅列一系列points來闡述哪些是推薦做的,哪些是不推薦做的。

BACKBONE

書寫可維護(hù)的代碼

bug是應(yīng)用程序的天敵,在現(xiàn)實世界中,沒有應(yīng)用程序是沒有bug的。但是bug的修復(fù)成本往往是比較昂貴的。故,為了降低修復(fù)bug的成本,書寫維護(hù)性高的代碼是及其有意義的。

那么,可維護(hù)的代碼應(yīng)該包括哪些基本要素呢?

  • 可讀的
    我們不推薦在代碼中使用過多的hack style的技巧,以及過多的復(fù)雜判斷或者其他邏輯等。因為這會讓后續(xù)的維護(hù)者花費(fèi)更多的時間去理解代碼。
  • 一致的
    代碼應(yīng)該在空間和行為上具有一致性。
  • 可預(yù)測的
    可預(yù)測的含義比較廣,比如,應(yīng)該把相似的邏輯放在一起,讓維護(hù)者可以預(yù)測你代碼的組織結(jié)構(gòu)。
  • 看上去像是同一個人寫的
    這一點(diǎn)比較好理解。同一份代碼中,所有的參與者都應(yīng)該遵循同一份code style。
  • 已記錄
    這里的已記錄我個人猜測的含義應(yīng)該是在適當(dāng)?shù)牡胤綉?yīng)該有明確的注釋,并且在復(fù)雜邏輯出應(yīng)該注明邏輯實現(xiàn)思想。

最小全局變量

JavaScript語言通過函數(shù)來管理作用域。在函數(shù)內(nèi)部聲明的變量只能在這個函數(shù)的內(nèi)部使用,外部不可用。另一方面,全局變量指在函數(shù)外部聲明的變量,或者未聲明就直接使用的變量(我們一般不推薦這種做法,因為可能會導(dǎo)致各種意想不到的問題。)。

就一般而言,我們指的全局變量都是指瀏覽器環(huán)境,往往就是指window這個對象。

全局變量的問題

JavaScript中全局變量導(dǎo)致的問題最常見的就是命名沖突。比如你先引入了JQuery,然后又自己定義了一個全局變量$,明顯的,JQuery$變量就被你覆蓋了。

一般而言,我們推薦盡量少的使用全局變量,可以通過命名空間或者自執(zhí)行函數(shù)來減少命名空間的使用。

全局變量還有另外一個常見的問題,就是不通過var關(guān)鍵字聲明的變量,都將隱式的轉(zhuǎn)變成全局變量。所以我們推薦所有的變量都使用var關(guān)鍵字進(jìn)行聲明。

另一個創(chuàng)建隱式全局變量的反例就是使用任務(wù)鏈進(jìn)行部分var聲明。如下代碼,

function foo() {
    var a = b = 0;
    // ...
}

這里,變量b將會被隱式轉(zhuǎn)換為全局變量。此現(xiàn)象發(fā)生的原因在于JavaScript使用從右到左的賦值。

首先,是賦值表達(dá)式b = 0,此情況下b是未聲明的。這個表達(dá)式的返回值是0,然后這個0就分配給了通過var定義的局部變量a。換句話說,相當(dāng)于你輸入了,

var a = (b = 0);

所以,當(dāng)需要進(jìn)行一次性進(jìn)行多個變量賦值時,我們一般推薦如下的做法,

function foo() {
    var a, b;
    // ...
}

忘記var的副作用

隱式的全局變量和明確定義的全局變量間有些小的差異,就是能否通過delete操作符讓變量未定義的能力。

  • 通過var創(chuàng)建的全局變量是不能被刪除的。
  • var創(chuàng)建的隱式全局變量是能被刪除的。(被刪除后,變量的值變?yōu)?code>undefined)

這點(diǎn)說明了什么問題呢?

在技術(shù)上,隱式全局變量并不是真正的全局變量,但他們是全局變量的屬性。而屬性是可以通過delete操作符刪除的,而變量是不可以的。

現(xiàn)在ES5的strict模式下,未聲明的變量工作時拋出一個錯誤。

ps:這點(diǎn)老實說,之前我也不知道。:(

單var形式

在函數(shù)頂部使用單var語句是比較有用的一種形式,其好處在于,

  • 提供了一個單一的地方去尋找功能所需要的所有局部變量
  • 防止變量在定義之前使用
  • 幫助你記住聲明的全局變量
  • 減少代碼量
  • 利于壓縮工具的壓縮

var形式長得就像下面的這個樣子,

function foo() {
    var a = 1,
        b = 2,
        sum = a + b,
        myobject = {},
        i,
        j;
    // more code...
}

這種單var形式的變量聲明好處多多,但是有一個不好的地方就是不利于調(diào)試。因為調(diào)試時,單步就直接把var語句執(zhí)行完畢了,這樣你可能就看不到類似sum = a + b這種運(yùn)算表達(dá)式的細(xì)節(jié)了。

預(yù)解析

在JavaScript中,你可以在函數(shù)的任意位置進(jìn)行var語句聲明,并且他們就好象是在函數(shù)頂部聲明一樣發(fā)揮作用。這種行為稱為hoisting(懸置/置頂解析/預(yù)解析)。

當(dāng)你使用了一個變量,然后不久在函數(shù)中又重新聲明的話,就可能產(chǎn)生邏輯錯誤。對于JavaScript,只要你的變量是在同一個作用域中(同一函數(shù)),它都被當(dāng)做是聲明的,即使是它是在var聲明前使用的。

ps:我本人以前就踩過這種坑!

讓我們來看一個例子,

// 反例
myname = "global"; // 全局變量
function func() {
    alert(myname); // "undefined"
    var myname = "local";
    alert(myname); // "local"
}
func();

在這個例子中,你可能會以為第一個alert彈出的是global,第二個彈出loacl。這種期許是可以理解的,因為在第一個alert的時候,myname未聲明,此時函數(shù)肯定很自然而然地看全局變量myname,但是,實際上并不是這么工作的。第一個alert會彈 出undefined是因為myname被當(dāng)做了函數(shù)的局部變量(盡管是之后聲明的),所有的變量聲明都被懸置到函數(shù)的頂部了。

因此,為了避免這種混亂,最好是預(yù)先聲明你想使用的全部變量。

ps:大叔的解釋已經(jīng)夠好了,我就直接引用了,沒必要畫蛇添足了。

其實,上面的代碼就等同于下面,

myname = "global"; // global variable
function func() {
    var myname; // 等同于 -> var myname = undefined;
    alert(myname); // "undefined"
    myname = "local";
    alert(myname); // "local"
}
func();

for循環(huán)

在for循環(huán)中,你可以循環(huán)取得數(shù)組或是數(shù)組類似對象的值,譬如argumentsHTMLCollection對象。通常的循環(huán)形式如下,

// 次佳的循環(huán)
for (var i = 0; i < myarray.length; i++) {
    // 使用myarray[i]做點(diǎn)什么
}

這種形式的循環(huán)的不足在于每次循環(huán)的時候數(shù)組的長度都要去獲取下。這回降低你的代碼,尤其當(dāng)myarray不是數(shù)組,而是一個array-like對象的時候。

一般我們會采取緩存數(shù)組的長度這種方式來進(jìn)行循環(huán)遍歷,

for (var i = 0, max = myarray.length; i < max; i++) {
    // 使用myarray[i]做點(diǎn)什么
}

這樣,在這個循環(huán)過程中,你只檢索了一次長度值。

for-in循環(huán)

for-in循環(huán)應(yīng)該用在非數(shù)組對象的遍歷上,使用for-in進(jìn)行循環(huán)也被稱為枚舉。

for-in循環(huán)有兩點(diǎn)需要提一下,

  • 盡量不要使用for-in去遍歷數(shù)組對象
  • 使用hasOwnProperty方法可以過濾掉來自原型上的屬性和方法

擴(kuò)展內(nèi)置原型

在許久之前,有一個流行的JavaScript類庫叫做Prototype,他就是擴(kuò)展了原生對象的原型(我不明確現(xiàn)在是不是還是這樣的:(,因為我自己也沒用過這個類庫)。

不過,現(xiàn)在業(yè)內(nèi)基本都已經(jīng)達(dá)成共識,不推薦(或者不允許)擴(kuò)展原生對象的原型。因為這在多人合作或者大型項目造成諸多問題。

如果你嫌棄原生對象沒有提供足夠的方法,推薦你使用下面兩款工具類庫,

如果上面的工具庫還不能滿足你,你可以自己實現(xiàn)需要工具方法,但是請記住,不要掛載在原生對象的原型上!

避免隱式類型轉(zhuǎn)換

JavaScript是一門弱語言編程語言,他有一個強(qiáng)大的功能就是隱式類型自動轉(zhuǎn)換。這個功能有時間太強(qiáng)大了,會在你不知道的情況下進(jìn)行類型轉(zhuǎn)換,然后引起一些問題和混亂。

比如下面的例子,

var a = '11',
    b = 11,
    c = a + b;
console.log(c);

這里c的結(jié)果將會是1111,他是一個字符串??赡苓@種情況并不是你的本意。

另一方面,特別是在進(jìn)行條件判斷的時候,我們更推薦使用===!==,而不是==!=。

避免使用eval和with

我相信,大部分寫JavaScript的程序員甚至很少接觸到這兩個東西,甚至都沒聽說過(哈哈,有點(diǎn)夸張了)。先簡單說下evalwith的作用。

  • eval()的作用是,接受一個字符串,將此字符串當(dāng)作JavaScript代碼來處理
  • with(){}的作用是,指定{}內(nèi)代碼的this指向

eval在特定的情況下可能會比較有用一點(diǎn),比如進(jìn)行一些內(nèi)庫開發(fā)的時候,但是with的使用真的很少,基本上都是不推薦使用的。

這里我簡單的說一下使用eval可能會出現(xiàn)的幾個問題,

  • 存在安全隱患
    因為eval將接受到的字符串當(dāng)作JavaScript代碼來處理。如果接受到的字符串本身存在安全問題(比如來自網(wǎng)絡(luò)請求得到的),那么執(zhí)行這條字符串可能造成安全風(fēng)險。
  • 污染當(dāng)前作用域
    eval執(zhí)行時,其內(nèi)部使用的作用域是全局作用域。如果你在內(nèi)部定義了一個變量與全局變量名一致,那么就會覆蓋之前的全局變量。

使用parseInt()進(jìn)行數(shù)值轉(zhuǎn)換

parseInt是原生提供一個用于解析數(shù)字的工具方法。他接受一個字符串,將字符串中數(shù)字解析成數(shù)值型,如果遇到非數(shù)字型字符串,則解析過程停止。該函數(shù)還可以接受第二個參數(shù),表示數(shù)值的基數(shù)。

這里就簡單的說一下使用parseInt應(yīng)該注意的幾個方面,

  • 盡量傳入第二個參數(shù)
    通過的用法中都省略了第二個基數(shù)參數(shù),其實這是不應(yīng)該的。比如,如果我傳入的字符串為099這種,而且沒有傳入基數(shù)參數(shù),那么這個099將會被當(dāng)作八進(jìn)制數(shù)來處理,這明顯與我的本意相違背。

編碼規(guī)范相關(guān)

大叔這里還提了一下編碼規(guī)范相關(guān)的內(nèi)容。關(guān)于code style這個東西,我個人的看法是遵循一般性原則,細(xì)節(jié)適時調(diào)整即可。

比如說我個人的code style,我大致參考的是Google JavaScript Style Guide,當(dāng)然也不是完全100%的照搬,我也會做一些適當(dāng)?shù)恼{(diào)整。比如,google js style中說空格使用2個空格,但是我個人習(xí)慣使用4個空格。所以說,code style這東西不必太較真,就個人來說有個大致的參考即可;就公司團(tuán)隊來說,務(wù)必有一套人人都得遵守的規(guī)范,這是高質(zhì)量、高可維護(hù)代碼的基石之一。

這里,我僅羅列一下大叔提到的code style points,并不作具體說明,

  • 縮進(jìn)(Indentation)
  • 花括號(Curly Braces)
  • 花括號的位置(Opening Brace Location)
  • 空格(White Space)
  • 命名規(guī)范(Naming Conventions)
  • 以大寫字段寫構(gòu)造函數(shù)(Capitalizing Constructors)
  • 分割單詞(Separating Words)
  • 其他命名形式(Other Naming Patterns)
  • 注釋(Writing Comments)

這里,打個小廣告,我個人code style的repo。

總結(jié)

如何編寫高質(zhì)量的JavaScript代碼?

ps:以下都是個人觀點(diǎn)

一句話可以概括,把握語言本質(zhì),較真語言細(xì)節(jié),輔以實踐經(jīng)驗。

說的簡單通俗點(diǎn),就是


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號