天天看點

前端知識點第二章 概要

轉載于(支援作者)-----https://github.com/TooBug/javascript.patterns/blob/master/chapter2.markdown#%E5%BF%98%E8%AE%B0var%E6%97%B6%E7%9A%84%E5%89%AF%E4%BD%9C%E7%94%A8

第二章 概要

本章将概要介紹一些編寫高品質JavaScript的最佳實踐、模式和習慣,比如避免全局變量、使用單

var

聲明、預緩存循環中的

length

、遵守編碼約定等等。本章還包括一些程式設計習慣,這些習慣跟具體的代碼關系不大,而是更多關注代碼建立的總體過程,包括撰寫API文檔、同僚評審以及使用JSLint。這些習慣和最佳實踐可以幫助你寫出更好更易讀和可維護性更好的代碼,當幾個月或數年後你再重讀你的代碼時,就會深有體會了。

編寫可維護的代碼

修複軟體bug成本很高,而且随着時間的推移,修複這些bug的成本會越來越高,尤其以出現在已經打包釋出的軟體中的bug為最甚。發現bug時立刻解決掉是最好的,但前提是你對你的代碼依然很熟悉,否則當你轉身投入到另外一個項目的開發中後,已經根本不記得當初的代碼的模樣了。當過了一段時間後你再去閱讀當初的代碼時你需要更多的時間:

  • 重新學習并了解面臨的問題
  • 了解用于問題的代碼

在大項目或者大公司的軟體開發中還有另一個問題,就是解決這個bug的人和制造這個bug的人往往不是同一個人(而發現bug的往往又是另外一個人)。是以不管是隔了很長時間重讀自己的代碼還是閱讀團隊内其他人的代碼,減少了解代碼所需的時間成本都是非常重要的。這對于公司的利益底線和工程師的幸福指數同樣重要,因為每個人都甯願去開發新的項目而不願花很多時間和精力去維護舊代碼。

軟體開發中的另一個普遍現象是,在讀代碼上花的時間要遠遠超過寫代碼的時間。當你專注于某個問題的時候,你往往會坐下來用一下午的時間寫出大量的代碼。在當時的場景下,這些代碼是可以正常運作的,但當應用趨于成熟,會有很多因素促使你重讀代碼、改進代碼或對代碼做微調。比如:

  • 發現了bug
  • 需要給應用添加新需求
  • 需要将應用遷移到新的平台中運作(比如當市場中出現了新的浏覽器時)
  • 代碼重構
  • 由于架構更改或者更換語言導緻代碼重寫

這些不确定因素帶來的後果是,少數人花幾小時寫的代碼需要很多人花幾個星期去閱讀它。是以,建立可維護的代碼對于一個成功的應用來說至關重要。

可維護的代碼意味着代碼是:

  • 可讀的
  • 風格一緻的
  • 可預測的
  • 看起來像是同一個人寫的
  • 有文檔的

本章接下來的部分會對這幾點深入講解。

減少全局變量

JavaScript使用函數來管理作用域,在一個函數内定義的變量稱作“本地變量”,本地變量在函數外部是不能被通路的。與之相對,“全局變量”是不在任何函數體内部聲明的變量,或者是直接使用而未聲明的變量。

每一個JavaScript運作環境都有一個“全局對象”,不在任何函數體内使用this就可以獲得對這個全局對象的引用。你所建立的每一個全局變量都是這個全局對象的屬性。為了友善起見,浏覽器會額外提供一個全局對象的屬性

window

,(一般)指向全局對象本身。下面的示例代碼展示了如何在浏覽器中建立或通路全局變量:

myglobal = "hello"; // 反模式
console.log(myglobal); // "hello"
console.log(window.myglobal); // "hello"
console.log(window["myglobal"]); // "hello"
console.log(this.myglobal); // "hello"
           

全局變量的問題

全局變量的問題是,它們在整個JavaScript應用或者是整個web頁面中是始終被所有代碼共享的。它們存在于同一個命名空間中,是以命名沖突的情況會時有發生,畢竟在應用程式的不同子產品中,經常會出于某種目的定義相同的全局變量。

同樣,在網頁中嵌入不是頁面開發者編寫的代碼是很常見的,比如:

  • 網頁中使用了第三方的JavaScript庫
  • 網頁中使用了廣告代碼
  • 網頁中使用了用以分析流量和點選率的第三方統計代碼
  • 網頁中使用了很多元件、挂件和按鈕等等

假設某一段第三方提供的腳本定義了一個全局變量result。随後你在自己寫的某個函數中也定義了一個全局變量result。這時,第二個變量就會覆寫第一個,會導緻第三方腳本工作不正常。

是以,為了讓你的腳本和這個頁面中的其他腳本和諧相處,要盡量少使用全局變量,這一點非常重要。本書随後的章節中會講到一些減少全局變量的技巧和政策,比如使用命名空間或者即時函數等,但減少全局變量最有效的方法還是堅持使用

var

來聲明變量。

在JavaScript中有意無意地建立全局變量是件很容易的事,因為它有兩個特性:首先,你可以不聲明而直接使用變量,其次,JavaScirpt中具有“隐式全局對象”的概念,也就是說任何不通過

var

聲明的變量都會成為全局對象的一個屬性(可以把它們當作全局變量)。(譯注:在ES6中可以通過

let

來聲明塊級作用域變量。)看一下下面這段代碼:

function sum(x, y) {
	// 反模式:隐式全局變量
	result = x + y;
	return result;
}
           

這段代碼中,我們直接使用了

result

而沒有事先聲明它。這段代碼的确是可以正常工作,但被調用後會産生一個全局變量

result

,這可能會導緻其他問題。

解決辦法是,總是使用var來聲明變量,下面代碼就是改進了的

sum()

函數:

function sum(x, y) {
	var result = x + y;
	return result;
}
           

另一種建立全局變量的反模式,就是在

var

聲明中使用鍊式指派的方法。在下面這個代碼片段中,

a

是局部變量,但

b

是全局變量,而作者的意圖顯然不是這樣:

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

為什麼會這樣呢?因為這裡的計算順序是從右至左的:首先計算表達式

b=0

,這裡的

b

是未聲明的;這個表達式的結果是

,然後通過var建立了本地變量

a

,并指派為

。換言之,可以将代碼寫成這樣:

var a = (b = 0);
           

如果變量b已經被聲明,這種鍊式指派的寫法是可以使用的,不會意外地建立全局變量,比如:

function foo() {
	var a, b;
	// ...
	a = b = 0; // 兩個都是本地變量
}
           
避免使用全局變量的另一個原因是出于可移植性考慮,如果你希望将你的代碼運作于不同的平台環境(宿主),那麼使用全局變量就非常危險。因為很有可能你無意間建立的某個全局變量在目前的平台環境中是不存在的,你以為可以安全地使用,而在另一個環境中卻是本來就存在的。

忘記var時的副作用

隐式建立的全局變量和顯式定義的全局變量之間有着細微的差别,就是通過

delete

來删除它們的時候表現不一緻。

  • 通過

    var

    建立的全局變量(在任何函數體之外建立的變量)不能被删除。
  • 沒有用

    var

    建立的隐式全局變量(不考慮函數内的情況)可以被删除。

也就是說,隐式全局變量并不算是真正的變量,但它們卻是全局對象的屬性。屬性是可以通過

delete

運算符删除的,而變量不可以被删除:

// 定義三個全局變量
var global_var = 1;
global_novar = 2; // 反模式
(function () {
	global_fromfunc = 3; // 反模式
}());

// 嘗試删除
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true

// 測試删除結果
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"
           

在ES5嚴格模式中,給未聲明的變量指派會報錯(比如這段代碼中提到的兩個反模式)。

通路全局對象

在浏覽器中,我們可以随時随地通過

window

屬性來通路全局對象(除非你定義了一個名叫

window

的局部變量)。但換一個運作環境這個

window

可能就換成了别的名字(甚至根本就被禁止通路全局對象了)。如果不想通過這種寫死

window

的方式來通路全局變量,那麼你可以在任意函數作用域内執行:

var global = (function () {
	return this;
}());
           

這種方式總是可以通路到全局對象,因為在被當作函數(而不是構造函數)執行的函數體内,

this

總是指向全局對象。但這種情況在ECMAScript5的嚴格模式中行不通,是以在嚴格模式中你不得不尋求其他的替代方案。比如,如果你在開發一個庫,你會将你的代碼包裝在一個即時函數中(在第四章會講到),然後從全局作用域給這個匿名函數傳入一個指向

this

的參數。

單var模式

在函數的頂部使用唯一一個

var

語句是非常推薦的一種模式,它有如下一些好處:

  • 可以在同一個位置找到函數所需的所有變量
  • 避免在變量聲明之前使用這個變量時産生的邏輯錯誤(參考下一小節“聲明提前:分散的var帶來的問題”)
  • 提醒你不要忘記聲明變量,順便減少潛在的全局變量
  • 代碼量更少(輸入代碼更少且更易做代碼優化)

var

模式看起來像這樣:

function func() {
	var a = 1,
		b = 2,
		sum = a + b,
		myobject = {},
		i,
		j;
	// 函數體…
}
           

你可以使用一個

var

語句來聲明多個變量,變量之間用逗号分隔,也可以在這個語句中加入變量初始化的部分。這是一種非常好的實踐方式,可以避免邏輯錯誤(所有未初始化的變量都被聲明了,且值為undefined),并增加了代碼的可讀性。過段時間後再看這段代碼,你可以從初始化的值中大概知道這個變量的用法,比如你一眼就可看出某個變量是對象還是整數。

你可以在聲明變量時做一些額外的工作,比如在這個例子中就寫了

sum=a+b

這種代碼。另一個例子就是當代碼中用到對DOM元素時,你可以把DOM引用指派的操作也放在這個變量聲明語句中,比如下面這段代碼:

function updateElement() {
	var el = document.getElementById("result"),
		style = el.style;
	// 使用el和style…
}
           

聲明提前:分散的var帶來的問題

JavaScript允許在函數的任意地方寫任意多個

var

語句,但它們的行為會像在函數體頂部聲明變量一樣,這種現象被稱為“聲明提前”,當你在聲明語句之前使用這個變量時,可能會造成邏輯錯誤。對于JavaScript來說,一旦在某個作用域(同一個函數内)裡聲明了一個變量,那麼這個變量在整個作用域内都是存在的,包括在

var

聲明語句之前的位置。看一下這個例子:

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

這個例子中,你可能會期望第一個

alert()

彈出“global”,第二個

alert()

彈出“local”。這種結果看起來是合乎常理的,因為在第一個

alert()

執行時,

myname

還沒有被聲明,這時就應該“尋找”全局變量

myname

。但實際情況并不是這樣,第一個

alert()

彈出“undefined”,因為

myname

已經在函數内被聲明了(盡管聲明語句在後面)。所有的變量聲明都會被提前到函數的頂部,是以,為了避免類似帶有“歧義”的程式邏輯,最好在使用之前一起聲明它們。

上一個代碼片段等價于下面這個代碼片段:

myname = "global"; // 全局變量
function func() {
	var myname; // 等價于 -> var myname = undefined;
	alert(myname); // "undefined"
	myname = "local";
	alert(myname); // "local"
}
func();
           
這裡有必要對“變量提前”做進一步補充,實際上從JavaScript引擎的工作機制上看,這個過程稍微有點複雜。代碼處理經過了兩個階段:第一階段是建立變量、函數和形參,也就是預編譯的過程,它會掃描整段代碼的上下文;第二階段是在代碼的運作時(runtime),這一階段将建立函數表達式和一些非法的辨別符(未聲明的變量)。(譯注:這兩個階段并沒有包含代碼的執行,是在執行前的處理過程。)從實用性角度來講,我們更願意将這兩個階段歸成一個概念“變量提前”,盡管這個概念并沒有在ECMAScript标準中定義,但我們常常用它來解釋預編譯的行為過程。

for循環

for

循環中,可以對數組或類似數組的對象(比如

arguments

HTMLCollection

對象)進行周遊,通常

for

循環模式形如:

// 非最優的循環方式
for (var i = 0; i < myarray.length; i++) {
	// 通路myarray[i]…
}
           

這種模式的問題是,每次周遊都會通路數組的length屬性,這會降低代碼運作效率,特别是當

myarray

不是一個數組而是一個

HTMLCollection

對象的時候。

HTMLCollection

是由DOM方法傳回的對象集合,比如:

  • document.getElementsByName()
  • document.getElementsByClassName()
  • document.getElementsByTagName()

還有一些

HTMLCollection

是在DOM标準誕生之前就已經在用了并且現在仍然可用,包括:

  • document.images

    頁面中所有的IMG元素

  • document.links

    頁面中所有的A元素

  • document.forms

    頁面中所有的表單

  • document.forms[0].elements

    頁面中第一個表單的所有字段

這些對象的問題在于,它們都會實時查詢文檔(HTML頁面)中的對象。也就是說每次通過它們通路集合的

length

屬性時,總是都會去查詢DOM,而DOM操則是很耗資源的。

更好的辦法是在

for

循環中緩存要周遊的數組的長度,比如下面這段代碼:

for (var i = 0, max = myarray.length; i < max; i++) {
	// 通路myarray[i]…
}
           

通過這種方法隻需要擷取

length

一次,然後在整個循環過程中使用它。

不管在什麼浏覽器中,在周遊

HTMLCollection

時緩存

length

都可以讓程式執行的更快,可以提速2倍(Safari3)到190倍(IE7)不等。更多細節可以參照Nicholas Zakas的《高性能JavaScript》,這本書也是由O'Reilly出版。

需要注意的是,當你在循環過程中需要修改這個元素集合(比如增加DOM元素)時,你可能需要更新

length

按照“單

var

模式”,你可以将

var

提到循環的外部,比如:

function looper() {
	var i = 0,
		max,
		myarray = [];
	// …
	for (i = 0, max = myarray.length; i < max; i++) {
		// 通路myarray[i]…
	}
}
           

當你越來越依賴“單

var

模式”時,帶來的好處就是提高了代碼的一緻性。而缺點則是在重構代碼的時候不能直接複制粘貼一個循環體,比如,你正在将某個循環從一個函數複制至另外一個函數中,那麼必須確定

i

max

也複制到新函數裡,并且需要從舊函數中将這些沒用的變量删除掉。

最後一個需要對循環做出調整的地方是将i++替換成為下面兩者之一:

i = i + 1
i += 1
           

JSLint提示你這樣做,是因為

++

--

實際上降低了代碼的可讀性,如果你覺得無所謂,可以将JSLint的

plusplus

選項設為

false

(預設為

true

)。稍後,在本書所介紹的最後一個模式中用到了:

i += 1

關于這種

for

模式還有兩種變化的形式,做了少量改進,原因有二:

  • 減少一個變量(沒有

    max

  • 減量循環至0,這種方式速度更快,因為和零比較要比和非零數字或數組長度比較要高效的多

第一種變化形式是:

var i, myarray = [];
for (i = myarray.length; i--;) {
	// 通路myarray[i]…
}
           

第二種變化形式用到了while循環:

var myarray = [],
	i = myarray.length;
while (i--) {
	// 通路myarray[i]…
}
           

這些小改進隻能展現在對性能要求比較苛刻的地方,此外,JSLint不推薦使用

i--

for-in循環

for-in

循環用于對非數組對象進行周遊。通過

for-in

進行循環也被稱作“枚舉”(enumeration)。

從技術上講,

for-in

循環同樣可以用于數組(JavaScript中數組也是對象),但不推薦這樣做。當數組對象被擴充了自定義函數時,可能會産生邏輯錯誤。另外,

for-in

循環中屬性的周遊順序是不固定的,是以最好數組使用普通的

for

循環,對象使用

for-in

循環。

可以使用對象的

hasOwnProperty()

方法過濾來自原型鍊中繼承來的屬性,這一點非常重要。看一下這段代碼:

// 對象
var man = {
	hands: 2,
	legs: 2,
	heads: 1
};
// 在代碼的另一個地方給所有的對象添加了一個方法
if (typeof Object.prototype.clone === "undefined") {
	Object.prototype.clone = function () {};
}
           

在這個例子中,我們使用對象字面量定義了一個名叫

man

的對象。在代碼中的某個地方(可以是

man

定義之前也可以是之後),給

Object

的原型增加了一個方法

clone()

。原型鍊是實時的,這意味着所有的對象都可以通路到這個新方法。要想在枚舉

man

的時候避免枚舉出

clone()

方法,就需要調用

hasOwnProperty()

來過濾來自原型的屬性。如果不做過濾,

clone()

也會被周遊到,這是我們不希望看到的:

// 1.for-in循環
for (var i in man) {
	if (man.hasOwnProperty(i)) { // filter
		console.log(i, ":", man[i]);
	}
}
/*
控制台中的結果
hands : 2
legs : 2
heads : 1
*/

// 2.反模式:
// 不使用hasOwnProperty()過濾的for-in循環
for (var i in man) {
	console.log(i, ":", man[i]);
}
/*
控制台中的結果
hands : 2
legs : 2
heads : 1
clone: function()
*/
           

另外一種調用

hasOwnProperty()

的方法是通過

Object.prototype

來調用,像這樣:

for (var i in man) {
	if (Object.prototype.hasOwnProperty.call(man, i)) { // 過濾
		console.log(i, ":", man[i]);
	}
}
           

這種做法的好處是,在

man

對象中重新定義了

hasOwnProperty

方法的情況下,可以避免調用時的命名沖突。為了避免查找屬性時從

Object

對象一路找到原型的冗長過程,你可以定義一個變量來“緩存”住它:

var i,
	hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
	if (hasOwn.call(man, i)) { // 過濾
		console.log(i, ":", man[i]);
	}
}
           
嚴格說來,省略

hasOwnProperty()

并不是一個錯誤。根據具體的任務以及你對代碼的自信程度,你可以省略掉它以提高一些程式執行效率。但當你對目前要周遊的對象不确定的時候,添加hasOwnProperty()則更加保險些。

這裡介紹一種格式上的變種(這種寫法無法通過JSLint檢查),這種寫法在

for

循環所在的行加入了

if

判斷條件,他的好處是能讓循環語句讀起來更完整和通順(“如果元素包含屬性X,則對X做點什麼”):

// 警告:無法通過JSLint檢查
var i,
	hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // 過濾
	console.log(i, ":", man[i]);
}
           

(不)擴充内置原型

我們可以擴充構造函數的

prototype

屬性來為構造函數增加功能,這個特性非常強大,但有時會強大到超過我們的掌控。

給内置構造函數如

Object()

Array()

Function()

擴充原型看起來非常誘人,但這種做法會嚴重降低代碼的可維護性,因為它會讓你的代碼變得難以預測。對于那些基于你的代碼來做開發的開發者來說,他們更希望使用原生的JavaScript方法來保持代碼的一緻性,而不願意使用你所添加的方法。

另外,如果将屬性添加至原型中,很可能導緻原型上的屬性在那些不使用

hasOwnProperty()

做過濾的循環中被周遊出來,進而造成混亂。

是以,不擴充内置對象的原型是最好的,你也可以自己定義一個規則,僅當下列條件滿足時才考慮擴充内置對象的原型:

  1. 未來的ECMAScript版本或者JavaScirpt會将你将要實作的方法添加為内置方法。比如,你可以實作ECMAScript5定義的一些方法,直到浏覽器更新至支援ES5。這樣,你隻是提前定義了這些方法。
  2. 當某個屬性或者方法是你在其它地方實作過的,或者是某個JavaScript引擎或浏覽器的一部分,而你檢查時又發現它不存在時。
  3. 在有充分的文檔說明,并且和團隊其他成員做了溝通的時候。

如果你遇到這三種情況之一,你可以給内置原型添加自定義方法,寫法如下:

if (typeof Object.protoype.myMethod !== "function") {
	Object.protoype.myMethod = function () {
		// 實作…
	};
}
           

switch模式

你可以通過下面這種模式來增強

switch

語句的可讀性和健壯性:

var inspect_me = 0,
	result = '';
switch (inspect_me) {
case 0:
	result = "zero";
	break;
case 1:
	result = "one";
	break;
default:
	result = "unknown";
}
           

這個簡單的例子所遵循的風格約定如下:

  • 每個

    case

    switch

    對齊(這裡不考慮花括号相關的縮進規則)。
  • 每個

    case

    中的代碼整齊縮進。
  • 每個

    case

    都以

    break

    作為結束。
  • 避免連續執行多個case語句塊(省略break時),如果你堅持認為連續執行多個

    case

    語句塊是最好的方法,請務必補充文檔說明,對于其他人來說,會覺得這種情況是錯誤的寫法。
  • default

    結束整個

    switch

    ,以確定即便是在找不到比對項時也有合理的結果。

避免隐式類型轉換

在JavaScript對變量進行比較時會有一些隐式的資料類型轉換。比如諸如

false == 0

"" == 0

之類的比較都傳回

true

為了避免隐式類型轉換對程式造成幹擾,推薦使用

===

!==

運算符,它們除了比較值還會比較類型:

var zero = 0;
if (zero === false) {
	// 不會執行,因為zero是0,不是false
}
// 反模式
if (zero == false) {
	// 代碼塊會執行…
}
           

有一種觀點認為當

==

夠用的時候就不必使用

===

。比如,當你知道

typeof

的傳回值是一個字元串,就不必使用全等運算符。但JSLint卻要求使用全等運算符,這無疑會提高代碼風格的一緻性,并減少了閱讀代碼時的思考量(“這裡使用

==

是故意的還是無意的?”)。

避免使用eval()

當你想使用

eval()

的時候,不要忘了那句話“

eval()

 is evil”(

eval()

是魔鬼)。這個函數的參數是一個字元串,它會将傳入的字元串作為JavaScript代碼執行。如果用來解決問題的代碼是事先知道的(在運作之前),則沒有理由使用

eval()

。如果需要在運作時動态生成并執行代碼,那一般都會有更好的方式達到同樣的目的,而非一定要使用

eval()

。例如,通路動态屬性時可以使用方括号:

// 反模式
var property = "name";
alert(eval("obj." + property));
// 更好的方式
var property = "name";
alert(obj[property]);
           

eval()

還有安全隐患,因為你有可能會運作一些被幹擾過的代碼(比如一段來自于網絡的代碼)。這是一種在處理Ajax請求所傳回的JSON資料時比較常見的反模式。這種情況下最好使用浏覽器的内置方法來解析JSON資料,以確定代碼的安全性和資料的合法性。如果浏覽器不支援

JSON.parse()

,你可以使用JSON.org所提供的庫。

值得一提的是,多數情況下,給

setInterval()

setTimeout()

Function()

構造函數傳入字元串的情形和

eval()

類似,這種用法也是應當避免的,因為這些情形中JavaScript最終還是會執行傳入的字元串參數:

// 反模式
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// 更好的方式
setTimeout(myFunc, 1000);
setTimeout(function () {
	myFunc(1, 2, 3);
}, 1000);
           

new Function()

的用法和

eval()

非常類似,應當特别注意。這種構造函數的方式很強大,但經常會被誤用。如果你不得不使用

eval()

,你可以嘗試用

new Function()

來代替。這有一個潛在的好處,在

new Function()

中運作的代碼會在一個局部函數作用域内執行,是以源碼中所有用

var

定義的變量不會自動變成全局變量。還有一種方法可以避免

eval()

中定義的變量被轉換為全局變量,即是将

eval()

包裝在一個即時函數内(詳細内容請參見第四章)。

看一下這個例子,這裡隻有

un

成為全局變量污染了全局命名空間:

console.log(typeof un);// "undefined"
console.log(typeof deux); // "undefined"
console.log(typeof trois); // "undefined"

var jsstring = "var un = 1; console.log(un);";
eval(jsstring); // 列印出 "1"

jsstring = "var deux = 2; console.log(deux);";
new Function(jsstring)(); // 列印出 "2"

jsstring = "var trois = 3; console.log(trois);";
(function () {
	eval(jsstring);
}()); // 列印出 "3"

console.log(typeof un); // "number"
console.log(typeof deux); // "undefined"
console.log(typeof trois); // "undefined"
           

eval()

Function()

構造函數還有一個差別,就是

eval()

可以修改作用域鍊,而

Function

更像是一個沙箱。不管在什麼地方執行

Function()

,它都隻能看到全局作用域。是以它不會太嚴重的污染局部變量。在下面的示例代碼中,

eval()

可以通路并修改其作用域之外的變量,而

Function()

則不能(注意,使用

Function()

new Function()

是完全一樣的)。

(function () {
	var local = 1;
	eval("local = 3; console.log(local)"); // 列印出 3
	console.log(local); // 列印出 3
}());

(function () {
	var local = 1;
	Function("console.log(typeof local);")(); // 列印出 undefined
}());
           

使用parseInt()進行數字轉換

你可以使用

parseInt()

将字元串轉換為數字。函數的第二個參數是進制參數,這個參數應該被指定,但卻通常被省略。當字元串以0為字首時轉換就會出問題,例如,在表單中輸入日期的一個字段。ECMAScript3中以0為字首的字元串會被當作八進制數處理,這一點在ES5中已經有了改變。為了避免轉換類型不一緻而導緻的意外結果,應當總是指定第二個參數:

var month = "06",
	year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);
           

在這個例子中,如果省略掉parseInt的第二個參數,比如

parseInt(year)

,傳回的值是0,因為“09”被認為是八進制數(等價于

parseInt(year,8)

),但09是非法的八進制數。

字元串轉換為數字還有兩種方法:

+"08" // 結果為8
Number("08") // 結果為8
           

這兩種方法要比

parseInt()

更快一些,因為顧名思義

parseInt()

是一種“解析”而不是簡單的“轉換”。但當你期望将“08 hello”這類字元串轉換為數字,則必須使用

parseInt()

,其他方法都會傳回NaN。

代碼規範

确立并遵守代碼規範非常重要,這會讓你的代碼風格一緻、可預測,并且可讀性更強。團隊新成員通過學習代碼規範可以很快進入開發狀态,并寫出讓團隊其他成員易于了解的代碼。

在開源社群和郵件組中關于編代風格的争論一直不斷。(比如關于代碼縮進,用tab還是空格?)是以,如果你打算在團隊内推行某種編碼規範時,要做好應對各種反對意見的心理準備,而且要吸取各種意見。确定并遵守代碼規範非常重要,任何一種規範都可以,這甚至比代碼規範中的具體約定是怎麼樣的還要重要。

縮進

代碼如果沒有縮進就幾乎不能讀了,而不一緻的縮進會使情況更加糟糕,因為它看上去像是遵守了規範,但真正讀起來卻沒那麼順利。是以規範地使用縮進非常重要。

有些開發者喜歡使用tab縮進,因為每個人都可以根據自己的喜好來調整tab縮進的空格數,有些人則喜歡使用空格縮進,通常是四個空格。這都無所謂,隻要團隊每個人都遵守同一個規範即可,本書中所有的示例代碼都采用四個空格的縮進寫法,這也是JSLint所推薦的。(譯注:電子版中看到的是用tab縮進,本譯文也保留使用tab縮進。)

那麼到底什麼時候應該縮進呢?規則很簡單,花括号裡的内容應當縮進,包括函數體、循環(

do

while

for

for-in

)體、

if

語句、

switch

語句和對象字面量裡的屬性。下面的代碼展示了如何正确地使用縮進:

function outer(a, b) {
	var c = 1,
		d = 2,
		inner;
	if (a > b) {
		inner = function () {
			return {
				r: c - d
			};
		};
	} else {
		inner = function () {
			return {
				r: c + d
			};
		};
	}
	return inner;
}
           

花括号

在特定的語句中應當總是使用花括号,即便是在可省略花括号的情況下也應當如此。從技術角度講,如果

if

for

中隻有一個語句,花括号是可以省略的,但最好還是不要省略,這會讓你的代碼更加工整一緻而且易于修改。

假設有這樣一段代碼,

for

循環中隻有一條語句,你可以省略掉這裡的花括号,而且不會有文法錯誤:

// 不好的方式
for (var i = 0; i < 10; i += 1)
	alert(i);
           

但如果過了一段時間,你給這個循環添加了另一行代碼會怎樣?

// 不好的方式
for (var i = 0; i < 10; i += 1)
	alert(i);
	alert(i + " is " + (i % 2 ? "odd" : "even"));
           

第二個

alert

實際上在循環體之外,但這裡的縮進會讓你迷惑。從長遠考慮最好還是寫上花括号,即便是在隻有一個語句的語句塊中也應如此:

// 更好的方式
for (var i = 0; i < 10; i += 1) {
	alert(i);
}
           

同理,if條件句也應當如此:

// 不好的方式
if (true)
	alert(1);
else
	alert(2);

// 更好的試
if (true) {
	alert(1);
} else {
	alert(2);
}
           

左花括号的位置

開發人員對于左大括号的位置有着不同的偏好,在同一行呢還是在下一行?

if (true) {
	alert("It's TRUE!");
}
           

或者:

if (true)
{
	alert("It's TRUE!");
}
           

在這個例子中,這個問題隻是個人偏好問題。但有時候花括号位置的不同會影響程式的執行,因為JavaScript會“自動插入分号”。JavaScript對行尾是否有分号并沒有要求,它會自動将分号補全。是以,當函數的

return

語句傳回了一個對象字面量,而對象的左花括号和

return

又不在同一行時,程式的執行就和預期的不同了:

// 警告:傳回值和預期的不同
function func() {
	return
	{
		name: "Batman"
	};
}
           

可以看出程式作者的意圖是傳回一個包含了

name

屬性的對象,但實際情況不是這樣。因為return後會填補一個分号,函數的傳回值就是undefined。這段代碼等價于:

// 警告:傳回值和預期的不同
function func() {
	return undefined;
	// 下面的代碼不會運作…
	{
		name: "Batman"
	};
}
           

總結一下好的寫法,在特寫的語句中總是使用花括号,并且總是将左花括号與上一條語句放在同一行:

function func() {
	return {
		name: "Batman"
	};
}
           
關于分号也值得注意:和花括号一樣,應當總是使用分号,盡管在JavaScript解析代碼時會補全行末省略的分号,但嚴格遵守這條規則,可以讓代碼更加嚴謹,同時可以避免前面例子中所出現的歧義。

### 空格

空格的使用同樣有助于改善代碼的可讀性和一緻性。在寫英文句子的時候,在逗号和句号後面會使用間隔,在JavaScript中,你可以按照同樣的邏輯在表達式(相當于逗号)和語句結束(相對于完成了某個“想法”的表達)後面添加間隔。

适合使用空格的地方包括:

  • for循環中的分号之後,比如

    for (var i = 0; i < 10; i += 1) {...}

  • for循環中初始化多個變量,比如

    for (var i = 0, max = 10; i < max; i += 1) {...}

  • 用于分隔數組元素的逗号之後,比如

    var a = [1, 2, 3];

  • 對象屬性後的逗号以及名值對之間的冒号之後,比如

    var o = {a: 1, b: 2};

  • 函數參數中,比如

    myFunc(a, b, c)

  • 函數聲明的花括号之前,比如

    function myFunc() {}

  • 匿名函數表達式

    function

    之後,比如

    var myFunc = function () {};

另外,我們推薦在運算符和操作數之間也添加空格。也就是說在

+

-

*

=

<

>

<=

>=

===

!==

&&

||

+=

符号前後都添加空格。

// 适當且一緻的空格給代碼留了“呼吸空間”,使代碼更易讀
var d = 0,
	a = b + 1;
if (a && b && c) {
	d = a % c;
	a += d;
}

// 反模式,缺少或者不正确的空格使得代碼不易讀
var d= 0,
	a =b+1;
if (a&& b&&c) {
	d=a %c;
	a+= d;
}
           

最後,還應當注意,最好在花括号旁邊添加空格:

  • 在函數、

    if-else

    語句、循環、對象字面量的左花括号之前補充空格
  • 在右花括号和

    else

    或者

    while

    之間補充空格
垂直空白的使用經常被我們忽略,你可以使用空行來将代碼單元分隔開,就像文學作品中使用段落進行分隔一樣。

命名規範

另外一種可以提升你代碼的可預測性和可維護性的方法是采用命名規範。也就是說變量和函數的命名都遵守同樣的習慣。

下面是一些建議的命名規範,你可以原樣采用,也可以根據自己的喜好作調整。同樣,遵循規範要比規範本身是什麼樣更加重要。

構造函數命名中的大小寫

JavaScript中沒有類,但有構造函數,可以通過

new

來調用構造函數:

var adam = new Person();
           

由于構造函數畢竟還是函數,如果隻通過函數名就可分辨出它是構造函數還是普通函數是非常有用的。

首字母大寫可以提示你這是一個構造函數,而首字母小寫的函數一般隻認為它是普通的函數,不應該通過new來調用它:

function MyConstructor() {...}
function myFunction() {...}
           

下一章将介紹一些強制将普通函數用作構造函數的程式設計模式,但遵守我們所提到的命名規範會更好的幫助程式員閱讀源碼。

單詞分隔

當你的變量名或函數名中含有多個單詞時,單詞之間的分隔也應當遵循統一的規範。最常見的是“駝峰式”(camel case)命名,單詞都是小寫,每個單詞的首字母是大寫。

對于構造函數,可以使用“大駝峰式”(upper camel case)命名,比如

MyConstructor()

,對于函數和方法,可以采用“小駝峰式”(lower camel case)命名,比如

myFunction()

calculateArea()

getFirstName()

那麼對于那些不是函數的變量應當如何命名呢?變量名通常采用小駝峰式命名,還有一個不錯的做法是,變量所有字母都是小寫,單詞之間用下劃線分隔,比如,

first_name

favorite_bands

old_company_name

,這種方法可以幫助你區分函數和其他辨別符如原始類型資料或對象。

ECMAScript的屬性和方法均使用駝峰式命名,盡管包含多個單詞的屬性名稱并不多見(正規表達式對象的

lastIndex

ignoreCase

屬性)。

其他命名風格

有時開發人員使用命名規範來彌補或代替語言特性的不足。

比如,JavaScript中無法定義常量(盡管有一些内置常量比如

Number.MAX_VALUE

),是以開發者都采用了一種命名規範,對于那些程式運作周期内不會更改的變量使用全大寫字母來命名。比如:

// 常量,請勿修改
var PI = 3.14,
	MAX_WIDTH = 800;
           

除了使用大寫字母的命名方式之外,還有另一種命名規範:全局變量全大寫。這種命名方式和“減少全局變量”的約定相輔相成,并讓全局變量很容易辨認。

除了常量和全局變量的命名規範,這裡讨論另外一種命名規範,即私有變量的命名。盡管在JavaScript是可以實作真正的私有變量的,但開發人員更喜歡在私有成員或方法名之前加上下劃線字首,比如下面的例子:

var person = {
	getName: function () {
		return this._getFirst() + ' ' + this._getLast();
	},
	_getFirst: function () {
		// ...
	},
	_getLast: function () {
		// ...
	}
};
           

在這個例子中,

getName()

是一個公有方法,是确定的API的一部分,而

_getFirst()

_getLast()

則是私有方法。盡管這兩個方法本質上和公有方法沒有差別,但在方法名前加下劃線字首就是為了告知使用者不要直接使用這兩個私有方法,因為不能保證它們在下一個版本中還能正常工作。JSLint會對私有方法作檢查,除非設定了JSLint的

nomen

選項為

false

下面介紹一些

_private

風格寫法的變種:

  • 在名字尾部添加下劃線以表明私有,比如

    name_

    getElements_()

  • 使用一個下劃線字首表明受保護的屬性

    _protected

    ,用兩個下劃線字首表明私有屬性

    __private

  • 在Firefox中實作了一些非标準的内置屬性,這些屬性在開頭和結束都有兩個下劃線,比如

    __proto__

    __parent__

寫注釋

在寫代碼時,即便你認為你的代碼不會被别人讀到,也應該寫好注釋。因為當你對一個問題非常熟悉時,你會非常明白這些代碼的作用,但當過了幾個星期後再來讀這段代碼時,則需要絞盡腦汁的回想這些代碼在幹什麼。

你不必對那些淺顯易懂的代碼寫過多的注釋,比如每個變量、每一行都寫注釋。但你應該對所有的函數、它們的參數和傳回值進行注釋,除此之外,對于那些值得注意的或是比較怪異的算法和技術也應當寫好注釋。對于其他閱讀你代碼的人來說,注釋就是一種提示,隻要閱讀注釋、函數名和參數,就算不讀其它部分的代碼也能大概了解程式的邏輯。比如,這裡有五六行代碼完成了某個功能,如果有一行描述這段代碼功能的注釋,讀程式的人就不必再去關注代碼的實作細節了。代碼注釋的寫法并沒有硬性規定,但有些代碼片段(比如正規表達式)需要比代碼本身更多的注釋。

過時的注釋會造成誤導,這比不寫注釋還要糟糕。保持注釋的狀态為最新的習慣非常重要,盡管對很多人來說這很難做到。

在下一小節我們會講到,利用注釋可以自動生成文檔。

寫API文檔

很多人都覺得寫文檔是一件很枯燥而且吃力不讨好的事情,但實際情況并不是這樣。我們可以通過代碼注釋自動生成文檔,這樣就不用再去專門寫文檔了。很多人覺得這是一個不錯的點子,因為根據某些關鍵字和特定的格式自動生成可閱讀的參考手冊本身就是“某種程式設計”。

最早利用注釋生成API文檔的工具誕生自Java業界,這個工具名叫“javadoc”,和Java SDK(軟體開發工具包)一起提供,但這個創意迅速被其他語言借鑒。JavaScript領域有兩個非常優秀的開源工具,它們是JSDoc Toolkit(http://code.google.com/p/jsdoc-toolkit/)和YUIDoc(http://yuilibrary.com/projects/yuidoc)。

生成API文檔的過程:

  • 以特定的格式來寫代碼
  • 運作工具來對代碼和注釋進行解析
  • 釋出工具運作的結果,通常是HTML頁面

這種文法包括十幾種标簽(tag),寫法類似于:

/**
 * @tag value
 */
           

比如這裡有一個函數

reverse()

,可以對字元串進行反序操作。它的參數和傳回值都是字元串。給它補充注釋如下:

/**
* Reverse a string
*
* @param {String} input String to reverse
* @return {String} The reversed string
*/
var reverse = function (input) {
	// ...
	return output;
};
           

如你所見,

@param

是用來說明輸入參數的标簽,

@return

是用來說明傳回值的标簽,文檔生成工具最終會将這種帶注釋的源代碼解析成HTML文檔。

示例:YUIDoc

YUIDoc的初衷是為YUI(Yahoo! User Interface)庫生成文檔,但其實它也可以應用于任何項目。為了更充分的使用YUIDoc,你需要學習它的注釋規範,比如子產品和類的寫法。(盡管在JavaScript中其實是沒有類的概念的)。

讓我們看一個用YUIDoc生成文檔的完整例子。

圖2-1展示了最終生成的文檔的樣子,你可以根據項目需要定制HTML模闆,讓生成的文檔更加友好和個性化。

這裡提供了線上的demo,請參照http://jspatterns.com/book/2/。

這個例子中所有的應用作為一個子產品(myapp)放在一個檔案裡(app.js),後續的章節會更詳細的介紹子產品,現在隻需知道可以用一個YUIDoc的标簽來表示子產品即可。

圖2-1 YUIDoc生成的文檔

前端知識點第二章 概要

app.js

的開始部分:

/**
 * My JavaScript application
 *
 * @module myapp
 */
           

然後定義了一個空對象作為子產品的命名空間:

var MYAPP = {};
           

緊接着定義了一個包含兩個方法的對象

math_stuff

,這兩個方法分别是

sum()

multi()

/**
* A math utility
* @namespace MYAPP
* @class math_stuff
*/
MYAPP.math_stuff = {
	/**
	* Sums two numbers
	*
	* @method sum
	* @param {Number} a First number
	* @param {Number} b The second number
	* @return {Number} The sum of the two inputs
	*/
	sum: function (a, b) {
		return a + b;
	},

	/**
	* Multiplies two numbers
	*
	* @method multi
	* @param {Number} a First number
	* @param {Number} b The second number
	* @return {Number} The two inputs multiplied
	*/
	multi: function (a, b) {
		return a * b;
	}
};
           

這樣就完成了第一個“類”的定義,注意以下标簽:

  • @namespace

    包含對象的全局引用
  • @class

    代表一個對象或構造函數(JavaScript中沒有類)
  • @method

    定義對象的方法,并指定方法的名稱
  • @param

    列出函數需要的參數,參數的類型放在一對花括号内,後面跟參數名和描述
  • @return

    和@param類似,用以描述方法的傳回值,可以不帶名字

我們來實作第二個“類”,使用一個構造函數,并給這個構造函數的原型添加一個方法,看看YUIDoc在面對不同的對象建立方式時是如何工作的:

/**
* Constructs Person objects
* @class Person
* @constructor
* @namespace MYAPP
* @param {String} first First name
* @param {String} last Last name
*/
MYAPP.Person = function (first, last) {
	/**
	* Name of the person
	* @property first_name
	* @type String
	*/
	this.first_name = first;
	/**
	* Last (family) name of the person
	* @property last_name
	* @type String
	*/
	this.last_name = last;
};
/**
* Returns the name of the person object
*
* @method getName
* @return {String} The name of the person
*/
MYAPP.Person.prototype.getName = function () {
	return this.first_name + ' ' + this.last_name;
};
           

在圖2-1中可以看到生成的文檔中

Person

構造函數的生成結果,值得注意的部分是:

  • @constructor

     說明這個“類”其實是一個構造函數
  • @prototype

     和 

    @type

     用來描述對象的屬性

YUIDoc工具是與語言無關的,隻解析注釋塊,而不是JavaScript代碼。它的缺點是必須要在注釋中指定屬性、參數和方法的名字,比如,

@property first_name

。好處是一旦你熟練掌握YUIDoc,就可以用它對任何語言源碼生成文檔。

編寫易讀的代碼

這種編寫注釋塊來生成API文檔的做法可不僅僅是為了偷懶,它還有另外一個作用,就是通過回頭重看代碼來提高代碼品質。

随便一個作者或者編輯都會告訴你“編輯非常重要”,甚至是寫一本好書或好文章最最重要的步驟。将想法落實在紙上形成草稿隻是第一步,草稿确實可以給讀者提供不少資訊,但往往還不是重點最明晰、結構最合理、最符合閱讀習慣的呈現形式。

程式設計也是同樣的道理,當你坐下來解決一個問題的時候,這時的解決方案隻是一種“草案”,盡管能正常工作,但是不是最優的方法呢?是不是可讀性好、易于了解、可維護和更新?假設當你過一段時間後再來回頭看你的代碼,一定會發現很多需要改進的地方,比如需要重新組織代碼或删掉多餘的内容等等。這實際上就是在“整理”你的代碼了,可以很大程度上提高你的代碼品質。但事實卻不那麼如願,我們常常承受着高強度的工作,根本沒有時間來整理代碼,是以通過代碼注釋來寫文檔其實是個不錯的機會。

你往往會在寫注釋文檔的時候發現很多問題,也會重新思考代碼中的不合理之處,比如,某個方法中的第三個參數比第二個參數更常用,第二個參數多數情況下取值為

true

,是以就需要對這個方法進行适當的改造和包裝。

寫出易讀的代碼(或API),是指寫代碼時要有讓别人能輕易讀懂的意識。帶着這個意識,你就需要不斷思考采用更好的方法來解決手頭的問題。

說回“草稿”的問題,也算是“抱佛腳”的權宜之計,一眼看上去是有點“草”,不過至少是有用的,特别是當你處理的是一個關鍵項目時(比如人命關天時)。一個合适的思路是,你應當始終扔掉你所給出的第一個解決方案,雖然它是可以正常工作的,但畢竟是一個草稿,是一種僅用于驗證解決問題可行性的方案。事實上,第二個方案往往會更好,因為這時你對問題的了解會更加透徹。在産生第二個方案的過程中,不要允許自己去複制粘貼之前的代碼,這有助于阻止自己投機取巧利用之前的捷徑,最後産生不完美的方案。

同僚評審(Peer Reviews)

另外一種可以提高代碼品質的方法是組織互相評審。同僚評審可以用一些工具輔助,可以很正式很規範,也是一種開發流程中值得提倡的步驟。你可能覺得沒有時間去作代碼互相評審,沒關系,你可以讓坐在你旁邊的同僚讀一下你的代碼,或者和她(譯注:注意是“她”而不是“他”)一起過一遍你的代碼。

同樣,當你在寫API文檔或者其他文檔的時候,同僚評審能讓你的産出物更加清晰,因為你寫的文檔是本來就是讓别人讀的,你得讓别人通過文檔知道你所做的東西。

同僚評審是一種很好的實踐,不僅僅是因為它能讓代碼變得更好,更重要的是,在評審的過程中,評審人和代碼作者通過分享和讨論,兩人都能取長補短、互相促進。

如果你的團隊隻有你一個開發人員,找不出第二個人能給你作代碼評審,這也沒關系。你可以通過将你的代碼片段開源,或把有意思的代碼片段貼在部落格中,讓全世界的人為你評審。

另外一個很好的實踐是使用版本管理工具(CVS、SVN或Git),一旦有人修改并送出了代碼,就會發郵件通知組内成員。雖然大部分郵件都進入了垃圾箱,但總是會碰巧有人在工作間隙看到你所送出的代碼,并對代碼做出一些評價。

釋出時的代碼壓縮(Minify)

這裡所說的代碼壓縮(Minify)是指去除JavaScript代碼中的空格、注釋以及其他不必要的部分,用以減少JavaScript檔案的體積,降低網絡帶寬消耗。我們通常使用壓縮工具來進行壓縮,比如YUICompressor(Yahoo!)或Closure Compiler(Google),這可以減少頁面加載時間。壓縮用于釋出的的腳本是很重要的,壓縮後的檔案體積能減少至原來的一半以下。

下面這段代碼是壓縮後的樣子(這段代碼是YUI2庫中的事件子產品):

YAHOO.util.CustomEvent=function(D,C,B,A){this.type=D;this.scope=C||window;this.silent
=B;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent)
{}var E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new
YAHOO.util.CustomEvent(E,this,true);}...
           

除了去除空格、空行和注釋之外,壓縮工具還能縮短命名的長度(在保證代碼安全的前提下),比如這段代碼中的參數

A

B

C

D

。壓縮工具隻會重命名局部變量,因為更改全局變量會破壞代碼的邏輯,這也是要盡量使用局部變量的原因。如果你使用的全局變量是對DOM節點的引用,而且程式中多次用到,那麼最好将它指派給一個局部變量,這樣能提高查找速度,代碼也會運作的更快,此外還能提高壓縮比、加快下載下傳速度。

補充說明一下,Goolge Closure Compiler還會為了更高的壓縮比對全局變量進行壓縮(在“進階”模式中),這是很危險的,且對程式設計規範的要求非常苛刻。

對用于生産環境的腳本做壓縮是非常重要的步驟,因為它能提升頁面性能,但你應當将這個過程交給工具來完成。千萬不要試圖手寫“壓縮好的”代碼,你應當在編寫代碼時堅持使用語義化的變量命名,并保留足夠的空格、縮進和注釋。你寫的代碼是需要被人閱讀的,是以應當将注意力放在代碼可讀性和可維護性上,将代碼壓縮的工作交給工具去完成。

運作JSLint

在上一章我們已經介紹了JSLint,本章中也提到了數次。到現在你應該已經相信用JSLint檢查你的代碼是一種好的程式設計模式了。

JSLint的檢查點都有哪些呢?它會對本章讨論過的一些模式(單

var

模式、

parseInt()

的第二個參數、總是使用花括号)做檢查。JSLint還包括其他方面的檢查:

  • 不可達代碼(譯注:指永遠不可能運作的代碼)
  • 變量在聲明之前被使用
  • 不安全的UTF字元
  • 使用

    void

    with

    或者

    eval

  • 無法正确解析的正規表達式

JSLint是基于JavaScript實作的(它自己的代碼是可以通過JSLint檢查的),它提供了線上工具,也可以下載下傳使用,可以運作于很多種平台的JavaScript解析器。你可以将源碼下載下傳後在本地運作,支援的環境包括WSH(Windows Scripting Host,Windows)、JSC(JavaScriptCore,MacOSX)或Rhino(Mozilla開發的JavaScript引擎)。

将JSLint下載下傳後和你的代碼編輯器配置在一起是個很不錯的主意,這樣每次你儲存代碼的時候都會自動執行代碼檢查。(為它配置一個快捷鍵也很有用)。

小結

本章我們讨論了編寫可維護性代碼的意義,它不僅關系着軟體項目的成功與否,還關系到參與項目的工程師的“精神健康”和“幸福指數”。随後我們讨論了一些最佳實踐和模式,它們包括:

  • 減少全局對象,最好每個應用隻有一個全局對象
  • 函數都使用單

    var

    模式來定義,這樣可以将所有的變量放在同一個地方聲明,同時可以避免“聲明提前”給程式邏輯帶來的影響
  • for

    循環、

    for-in

    循環、

    switch

    語句、“避免使用

    eval()

    ”、不要擴充内置原型
  • 遵守統一的編碼規範(在任何必要的時候保持空格、縮進、花括号和分号)和命名規範(構造函數、普通函數和變量)。

本章還讨論了其他一些和代碼本身無關的實踐,這些實踐和編碼過程緊密相關,包括寫注釋、寫API文檔、組織同僚評審、不要試圖去手動“壓縮”(minify)代碼而犧牲代碼可讀性、堅持使用JSLint來對代碼進行檢查。

轉載于:https://www.cnblogs.com/hellowoeld/p/7275449.html

繼續閱讀