如何寫出優雅的JavaScript代碼 ?
之前總結過一篇《如何寫出優雅的css代碼?》, 但是前一段時間發現自己的js代碼寫的真的很任性,沒有任何的優雅可言,于是這裡總結以下寫js時應當注意的問題,注:這篇文章多為參考Nicholas C. Zakas 的《編寫可維護的JavaScript》一書。
js中有合适的縮進層級才能使得代碼在閱讀起來更容易,一般有兩種方法:直接使用制表符tab、使用四個空格來縮進。
個人比較推薦使用四個空格,因為制表符的問題在于不同的文本編輯器對制表符的表現是不一緻的,是以說如果換了一個文本編輯器, 格式可能就不是自己想要的形式了。
而四個空格可能略顯麻煩,但是在sublime中我們可以自行設定tab代表四個空格,在preferrence下面的setting中的個人設定中添加:
這時就可以發現tab表示四個空格。
我們知道分析器的自動分号插入(Automatic Semicon Insertion,ASI) 機制,這樣即使我們不寫分号ASI也會幫助我們插入分号,但是缺點在于我們不可能記住自動插入分号的所有規則,寫在代碼壓縮的過程總可能導緻混亂,
是以在語句結尾使用分号為最佳實踐。
如果一個行的長度過長,就會出現橫向滾動條,是以當行的長度超過80個字元時我們最好換行。
注意點一: 換行後保持兩個層次的縮進(8個空格) 注意點二: 換行後一般上一行的末尾是符号,下一行的開始沒有符号。
如:
一種特殊情況是:如果是在變量聲明,那麼換行的下一行就要對齊,如下:
對于聯系緊密的代碼之間我們就可以不使用空行,而對于聯系不緊密的代碼之間就可以使用空行來提高可讀性。
比如在方法和方法之間、 在注釋的上一行使用空行、 在方法内的邏輯片段之間插入空行都是不錯的主意。
對于命名方式,我們不推薦使用_(下劃線)的命名方式,很多大公司遵從的都是駝峰式命名方法。
由于變量就是在聲明一個事物,用名詞; 而函數是在做一件事情,是以用動詞;以之作為區分。
5.1 變量的命名如果有字首則應當是名詞(與函數名分開),并盡量使用count、length、size等可以直接展現出值的資料類型的變量名,而i、j、k一般是在循環中使用的。
可以發現不好的寫法中變量看起來像是函數,容易造成誤導。
5.2 函數的命名如果有字首則應當四動詞(與變量分開)
5.3 常量的命名使用大寫字母+下劃線以區分常量
變量的值是可變的,常量的值是不變的,使用不同的命名方式可以更好的區分,如:
5.4 構造函數一般使用大駝峰命名法以和普通的函數差別,且其用名詞,因為是建立某個類型的執行個體。
直接量就是字元串、數字、布爾值、null 和 undefined這些。
6.1 字元串
字元串推薦使用雙引号,值得注意的是内部如果還有雙引号則需要轉義。
6.2 數字
為了避免歧義,盡量不要使用省略小數點前或小數點後的數字的習慣。
6.3 null
使用null的情況多是作為對象的占位符(placeholder)。
6.4 undefined
undefined 和 null是不一樣的, 盡管 undefined == null 會傳回true,但是我們還是應該嚴格區分, undefined表示這個變量沒有被初始化。 null是對象的占位符。
6.5 對象直接量
我們建立對象的方式就死對象字面量的方式,更加高效和整潔。
6.6 數組直接量
同對象直接量一樣,更推薦數組直接量。如下:
var colors = new Array("blue","yellow","red"); 這是不好的做法。
var colors = ["blue", "yellow", "red"]; 這是我們推薦的做法。
注釋雖然不是執行一個程式的必要部分,但是對于更好地閱讀和了解代碼,注釋是必不可少的。
可以看到,在上面的例子中我私用了兩種注釋的方式。
第一種: 在需要注釋的語句上面注釋,保持同樣的縮進,且在這個注釋上面使用空行,以明确表示注釋是給下面的語句的。
第二種:在需要注釋的語句後面注釋,且在語句的最後和//之間使用空格區分開來。
可以看到,在// 之後我們習慣使用空格區分開來。
這是多行注釋的最好方式,實際上我們隻要把多行注釋放在 /* */ 之間即可,但是使用上面的做法會更好一些。
注意點一: 注釋上方要有空行,必不可少。
注意點二: 注釋内容和語句要保持共同的縮進。
注意點三: 在*之後我們希望有一個空格,然後再書寫注釋的内容。
通用的規則是在代碼不清晰時需要添加注釋,否則就是畫蛇添足。
3.1 難于了解的代碼添加注釋
比如嵌套關系較多,傳遞參數不明顯等等。
3.2 可能被誤認為錯誤的代碼
比如在if()中的一個指派操作,而不是判斷是否相等的操作,其他人可能以為是你寫錯了而修改,導緻更為嚴重的錯誤,是以,
好的做法就是加上适當的注釋。
3.3 hack
一些使用的hack最好用注釋的方法來闡述說明。
注釋有時候也可以用來給一段代碼聲明額外的資訊。這些聲明的格式以單個單詞打頭并緊跟一個冒号。可使用的聲明如下所示:
TODO --- 說明代碼還沒有完成,應當包含下一步要做的事情。
HACK --- 說明代碼實作走了一個捷徑,應當包含為何使用HACK的原因,這也表明該問題可能會有更好的解決辦法。
XXX --- 說明代碼是有問題的并應當盡快修複。
FIXME---說明代碼是有問題的并應當盡快修複,重要性次于XXX。
REVIEW---說明代碼任何可能的改動都需要評審。
上述這些聲明可能在一行或多行注釋中使用,并且應當遵從同一般注釋類型相同的格式規則。如下所示:
這時我們推薦的做法:
注意點一: 聲明盡量使用一個var, 将沒有初始化的變量往後面放。
注意點二: values 中的值應當和[]有一個空格間隔, 數值和逗号之間不要空格, 逗号和下一個值之間需要一個空格。
注意點三: 無論是 for while 等,我們都建議使用 花括号,在for或if 後面的圓括号中,緊挨着圓括号内部不需要空格,在圓括号的兩邊需要使用空格,在所有的二進制操作符兩邊都使用空格,一進制操作符不使用空格。
盡量采用一個var來聲明,并且将不同的變量置于不同的行之中,且需要縮進相同,看上去才會更加規整。
函數名和開始圓括号之間不應當有空格,結束的圓括号和右邊的花括号之間應該留有一個空格。 右邊的花括号應當同function 關鍵字保持在同一行總。 參數名之間應當在逗号之後保留有一個空格。 如下所示:
函數調用和函數聲明是類似的,在函數名和參數外的圓括号之間都不應當有空格,如下所示:
js中允許使用匿名函數(本身沒有命名的函數), 并将匿名函數指派給變量或屬性。如下所示:
這種匿名函數同樣可以通過在最後一行加上一對圓括号來立即執行并傳回一個值,然後将這個值指派給變量,如下:
這個函數寫的很簡短當然可以看出來()是在立即調用,但是很多情況下我們是沒有辦法一眼看出來的,是以最好在外面加一個()來區分,如下:
這樣,在第一行中就有了一個辨別符(左圓括号), 表明這是一個立即執行的函數,添加一對圓括号并不會改變代碼的邏輯。
在使用==和!==操作符的時候,最容易發生的就是強制類型轉化 。 比如字元串和數字比較, 那麼字元串會首先轉換成為數字,再執行比較,實際上這并不是我們想要的,是以,最好使用 === 和 !== 這樣的運算符不會發生強制類型轉化。
我們知道string nuber 和 boolean 都是原始包裝類型,比如 var string = "zzw";
string是基本類型,為什麼還有方法可以調用呢? 這就是基本包裝類型的定義了。 因為,這條語句的表象背後JavaScript建立了String類型的新執行個體,緊跟着就被銷毀了,如下:
我們可以看到 雖然給string添加了屬性,但是在這個添加屬性的語句一旦執行完畢,這個基本包裝對象就會被銷毀了,是以後面就沒有辦法得到。
盡管我們可以使用這些基本包裝類型,但是還是強烈推薦大家避免使用他們,開發者的思路會常常在對象和原始值之間跳來跳去, 這樣會增加出bug的幾率。
建構軟體設計的方法有兩種:一種是把軟體做得很簡單以至于明顯找不到缺陷;另一種是把它做得很複雜以至于找不到明顯的缺陷。
我們知道使用者界面(User Interface, UI)是由html、css、js定義的。在實際場景中,html和css可以沒有js組成一個頁面,html和js可以沒有css組成一個頁面。
松耦合就是說一個元件和另一個元件的直接相關性,直接相關越少,則說明耦合就越松,就越容易調試。
在IE8和更早的版本中有一個特性即css表達式(CSS expression), 它允許在css中插入js,如下所示:
這樣會嚴重影響性能,是我們不推薦的。
通過腳本修改樣式的最流行的一種方法是:直接修改DOM元素的style屬性, 如 ele.style.color = "red";這是非常不好的一種做法,因為樣式資訊是通過JavaScript而非CSS來承載的,
當出現了樣式問題時,你通常會想着去查找css,直到你精疲力盡地排除了所有可能性,才會去JavaScript中查找樣式資訊。另外我們有時還會用ele.style.cssaText = "color: red; left: 10px;"這樣的方法一次性的定義多個屬性,但是這樣同樣具有可維護性問題。 (注:在這方面,阮一峰是推薦的,參照《如何寫出高性能網頁》)。
将css從JavaScript中抽離意味着所有的樣式資訊都應該保持在css中,當需要通過js來修改元素樣式的時候,最佳方法是操作CSS的className。如下:
是以說: css中的className可以成為css和js之間的橋梁,這就是很好的分離方式。
注:有一種使用style屬性的情形是可以接收的,當你給頁面的元素做定位,使其相對于另外一個元素或整個頁面重新定位,這樣在css中實在無法完成的,我們就可以使用js來完成。
比如<button onclick="doSomething()">click me </button>這就是典型的沒有分離的實踐。 問題在于:點選事件發生後,這個doSomething()函數必須存在,這是一個問題,而導緻報錯。 且如果修改了函數名又會導緻一系列的問題。
在html中嵌入js的另一個做法時将<script>放在html中,script标簽内又有js代碼。
當你需要調試一個文本或者是結構性的問題時,你更希望從HTML開始調試,而不是js,是以我們需要将html從js抽離。
在js中添加html往往是使用innerHTML來實作的,如:
那麼如何去解決這種問題呢?比如在我們點選了一個按鈕之後,我們希望得到一個彈出框,下面列舉了幾種做法:
方法一:從伺服器端加載
即将模版放在遠端伺服器,使用XMLHttpRequest對象來擷取外部标簽,這樣就不需要将html嵌入js中,而是向伺服器發送請求擷取字元串,然後以這種方式注入到頁面中。
當我們需要大量的HTML标簽到頁面中時,使用這種方式加載标簽時非常有幫助的。
方法二:簡單的用戶端模版
方法三: 複雜用戶端模版
什麼全局變量呢?
我們再全局環境下聲明的變量實際上就是window對象的屬性,在全局環境下聲明的函數就是window對象的方法。
雖然沒有定義在window上,但是實際上隻要是在全局環境中,就是屬于window的。
團隊開發的情況下,全局變量的問題是最多的,随着代碼量的增長,全局變量越多,引入錯誤的機率将會是以變得越來越高。為什麼這麼說呢? 可以從下面幾點進行分析
第一: 可能導緻命名沖突
全局變量很多時,你就有可能聲明了一個别人已經聲明過了的變量,由此導緻命名沖突。所有的變量都被定義成了局部變量,這樣的代碼才是最容易維護的。比如将alertVariable函數定義在外部檔案中和color分離開來,那麼這種依賴關系就很難追蹤到了。
第二:代碼的脆弱性
一個依賴于全局變量的函數即是深耦合于上下文環境中的,如果環境發生改變,那麼這個函數很有可能就失效了!!
如上例中,如果variable不複存在了,我們的alertVariable函數就失效了,但是如果variable被當作參數傳入,代碼的可維護性就會變得更好。如下所示:
像這樣,即使全局變量variable不存在了,我們的這個函數仍然是可用的。修改後的函數不再依賴于全局變量,是以任何對全局環境的修改都不會影響到它。
當定義函數的時候,最好盡可能多地将資料置于局部作用域中。在函數内定義的任何“東西”都應該采取這種寫法。
確定你的函數不會對全局變量有依賴,這将增強你的代碼的可測試性。
2.意外的全局變量
如果我們在一個函數中忘記使用var來聲明一個變量,這時就會導緻一個問題 --- 這個變量成為了一個全局變量。
3. 單全局變量方式
引入全局變量可以使得分隔的代碼塊之間進行通信。 是以不能說沒有全局變量,而是 依賴盡可能少的全局變量。
單全局變量模式在各種流行的JavaScript類庫中早已廣泛使用了。
YUI定義了唯一的一個YUI全局對象
jQuery 定義了兩個全局對象,$和jQuery ,隻有在$被其他類庫使用了的情況愛,為了避免沖突,我們需要應該使用jQuery。
Dojo 定義了一個dojo全局對象
Closure 類庫定義了一個goog全局對象。
單全局對象的意思是所建立的這個唯一全局對象時獨一無二的,并将你所有的功能代碼都挂載到這個全局對象上。 是以每一個全局變量都成為了你唯一全局對象的屬性,進而不會建立多個全局變量。
在寫代碼的時候,尤其是寫腳本,最需要注釋了。目前腳本、樣式的注釋格式都有一個已經成文的約定規範(這些約定規範最初是YUI Compressor制定的,詳見參考資料)了,如下:
其中說到這裡說到的壓縮工具有YUI Compressor 、Google Closure Compiler、gulp-uglify、grunt-contrib-uglify等,這些壓縮工具都支援以上的壓縮約定。常常把檔案的關鍵資訊放在第2種注釋内容裡,如檔案名稱、版本号、作者等。
關于這些關鍵資訊,都有一些關鍵詞和一定的格式來書寫。關鍵詞書寫格式為:
使用<code>@key desc</code>格式來書寫,常用的關鍵詞有:
其中,param關鍵詞的格式為:
我們可以安裝DocBlockr插件(sublime)。
A、先寫完你的函數
B、然後在函數的前面一行,輸入
C、然後回車,自動生成
D、并且在注釋塊中,按<code>@</code>鍵可以展開關鍵詞: