本文是深入理解JavaScript系列的第一篇讀文筆記,博客原文在這里。
本文是湯姆大叔在《JavaScript Patterns》的基礎(chǔ)上,可能參考了一些其他的文章,寫成的一篇最佳實踐Style文章。全文緊扣如何編寫高質(zhì)量JavaScript代碼這一問題,通過羅列一系列points來闡述哪些是推薦做的,哪些是不推薦做的。
bug是應(yīng)用程序的天敵,在現(xiàn)實世界中,沒有應(yīng)用程序是沒有bug的。但是bug的修復(fù)成本往往是比較昂貴的。故,為了降低修復(fù)bug的成本,書寫維護(hù)性高的代碼是及其有意義的。
那么,可維護(hù)的代碼應(yīng)該包括哪些基本要素呢?
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;
// ...
}
隱式的全局變量和明確定義的全局變量間有些小的差異,就是能否通過delete
操作符讓變量未定義的能力。
var
創(chuàng)建的全局變量是不能被刪除的。var
創(chuàng)建的隱式全局變量是能被刪除的。(被刪除后,變量的值變?yōu)?code>undefined)這點(diǎn)說明了什么問題呢?
在技術(shù)上,隱式全局變量并不是真正的全局變量,但他們是全局變量的屬性。而屬性是可以通過delete
操作符刪除的,而變量是不可以的。
現(xiàn)在ES5的strict
模式下,未聲明的變量工作時拋出一個錯誤。
ps:這點(diǎn)老實說,之前我也不知道。:(
在函數(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é)了。
在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)中,你可以循環(huán)取得數(shù)組或是數(shù)組類似對象的值,譬如arguments
和HTMLCollection
對象。通常的循環(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)應(yīng)該用在非數(shù)組對象的遍歷上,使用for-in
進(jìn)行循環(huán)也被稱為枚舉。
for-in
循環(huán)有兩點(diǎn)需要提一下,
for-in
去遍歷數(shù)組對象hasOwnProperty
方法可以過濾掉來自原型上的屬性和方法在許久之前,有一個流行的JavaScript類庫叫做Prototype,他就是擴(kuò)展了原生對象的原型(我不明確現(xiàn)在是不是還是這樣的:(,因為我自己也沒用過這個類庫)。
不過,現(xiàn)在業(yè)內(nèi)基本都已經(jīng)達(dá)成共識,不推薦(或者不允許)擴(kuò)展原生對象的原型。因為這在多人合作或者大型項目造成諸多問題。
如果你嫌棄原生對象沒有提供足夠的方法,推薦你使用下面兩款工具類庫,
如果上面的工具庫還不能滿足你,你可以自己實現(xià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)行條件判斷的時候,我們更推薦使用===
和!==
,而不是==
和!=
。
我相信,大部分寫JavaScript的程序員甚至很少接觸到這兩個東西,甚至都沒聽說過(哈哈,有點(diǎn)夸張了)。先簡單說下eval
和with
的作用。
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)險。eval
執(zhí)行時,其內(nèi)部使用的作用域是全局作用域。如果你在內(nèi)部定義了一個變量與全局變量名一致,那么就會覆蓋之前的全局變量。parseInt
是原生提供一個用于解析數(shù)字的工具方法。他接受一個字符串,將字符串中數(shù)字解析成數(shù)值型,如果遇到非數(shù)字型字符串,則解析過程停止。該函數(shù)還可以接受第二個參數(shù),表示數(shù)值的基數(shù)。
這里就簡單的說一下使用parseInt
應(yīng)該注意的幾個方面,
099
這種,而且沒有傳入基數(shù)參數(shù),那么這個099
將會被當(dāng)作八進(jìn)制數(shù)來處理,這明顯與我的本意相違背。大叔這里還提了一下編碼規(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,并不作具體說明,
這里,打個小廣告,我個人code style的repo。
如何編寫高質(zhì)量的JavaScript代碼?
ps:以下都是個人觀點(diǎn)
一句話可以概括,把握語言本質(zhì),較真語言細(xì)節(jié),輔以實踐經(jīng)驗。
說的簡單通俗點(diǎn),就是
更多建議: