JavaScript 有許多很棒的風格指南。因此,沒有必要再寫一本。相反地,本章描述元風格規則,並調查現有的風格指南和既定的最佳實務。我也會提到一些我喜歡的,但較有爭議的實務。這個想法是補充現有的風格指南,而不是取代它們。
這些是我喜歡的風格指南:
此外,還有兩個元風格指南
本節將涵蓋一些一般的程式碼撰寫提示。
撰寫一致程式碼有兩個重要的規則。第一個規則是,如果你開始一個新專案,你應該想出一個風格,記錄它,並在所有地方遵循它。團隊越大,就越需要透過 JSHint 等工具自動檢查是否遵守風格。在風格方面,有許多決定需要做出。大多數都有普遍同意的答案。其他則必須針對每個專案定義。例如:
var
陳述
第二個規則是,如果你加入一個現有的專案,你應該嚴格遵循它的規則(即使你不同意它們)。
對於大多數程式碼,用於閱讀的時間遠大於用於撰寫的時間。因此,讓前者盡可能容易非常重要。以下是這樣做的幾個準則
redBalloon
比 rdBlln
容易閱讀。
大多數程式碼庫都充滿了新的想法和概念。這表示如果你想要使用程式碼庫,你需要學習這些想法和概念。與教科書不同的是,程式碼的額外挑戰在於人們不會線性閱讀它。他們會隨意跳進任何地方,而且應該能夠大致理解正在發生什麼事。程式碼庫的三個部分有助於
許多聰明才智都用於這些最佳化。但是,你通常不需要它們。一方面,JavaScript 引擎變得越來越聰明,並自動最佳化遵循既定模式的程式碼速度。另一方面,縮小工具(第 32 章)會改寫你的程式碼,使其盡可能小。在兩種情況下,工具對你來說都很聰明,因此你不需要這樣。
有時候你別無選擇,只能最佳化程式碼的效能。如果你這樣做,請務必測量並最佳化正確的部分。在瀏覽器中,問題通常與 DOM 和 HTML 有關,而不是語言本身。
大多數的 JavaScript 程式設計師同意下列的最佳實務:
永遠使用嚴格相等(===
)和嚴格不等(!==
)。我建議永遠不要偏離這個規則。我甚至偏好下列兩個條件中的第一個,即使它們是等價的:
if
(
x
!==
undefined
&&
x
!==
null
)
...
// my choice
if
(
x
!=
null
)
...
// equivalent
在大括號用來界定程式碼區塊的語言中,大括號樣式決定您放置這些大括號的位置。在類 C 語言(例如 Java 和 JavaScript)中最常見的兩種大括號樣式是 Allman 樣式和 1TBS。
如果一個陳述式包含一個區塊,該區塊會被視為與陳述式的標頭有點分離:它的開頭大括號在自己的行中,與標頭具有相同的縮排層級。例如:
// Allman brace style
function
foo
(
x
,
y
,
z
)
{
if
(
x
)
{
a
();
}
else
{
b
();
c
();
}
}
在此,一個區塊與其陳述式的標頭更為緊密關聯;它在標頭之後,在同一行中開始。控制流程陳述式的本體總是放在大括號中,即使只有一個陳述式。例如:
// One True Brace Style
function
foo
(
x
,
y
,
z
)
{
if
(
x
)
{
a
();
}
else
{
b
();
c
();
}
}
1TBS 是(較舊的)K&R(Kernighan 和 Ritchie)樣式的變體。[21] 在 K&R 樣式中,函式是以 Allman 樣式撰寫,並且在大括號不必要時省略大括號,例如在單一陳述式的 then
狀況
// K&R brace style
function
foo
(
x
,
y
,
z
)
{
if
(
x
)
a
();
else
{
b
();
c
();
}
}
JavaScript 世界中的實際標準是 1TBS。它繼承自 Java,而且大多數的風格指南都建議使用它。其中一個原因是客觀的。如果你傳回一個物件文字,你必須將開頭的大括號放在與關鍵字 return
相同的行,像這樣(否則,自動分號插入會在 return
之後插入一個分號,表示沒有傳回任何東西;請參閱 陷阱:ASI 會意外地中斷陳述式)
return
{
name
:
'Jane'
};
顯然地,物件文字不是一個程式區塊,但如果兩者都使用相同的方式格式化,看起來會更一致,而且你不太可能會犯錯。
我個人的風格和偏好是
作為一個例外,如果一個陳述式可以用單行撰寫,我會省略大括號。例如
if
(
x
)
return
x
;
多個建構函式會產生物件,這些物件也可以透過文字建立。後者通常是較好的選擇:
var
obj
=
new
Object
();
// no
var
obj
=
{};
// yes
var
arr
=
new
Array
();
// no
var
arr
=
[];
// yes
var
regex
=
new
RegExp
(
'abc'
);
// avoid if possible
var
regex
=
/abc/
;
// yes
永遠不要使用建構函式 Array
來 建立一個包含給定元素的陣列。 使用元素初始化陣列(避免!) 會說明原因
var
arr
=
new
Array
(
'a'
,
'b'
,
'c'
);
// never ever
var
arr
=
[
'a'
,
'b'
,
'c'
];
// yes
本節收集了 不建議的耍小聰明範例。
不要巢狀 條件運算子:
// Don’t:
return
x
===
0
?
'red'
:
x
===
1
?
'green'
:
'blue'
;
// Better:
if
(
x
===
0
)
{
return
'red'
;
}
else
if
(
x
===
1
)
{
return
'green'
;
}
else
{
return
'blue'
;
}
// Best:
switch
(
x
)
{
case
0
:
return
'red'
;
case
1
:
return
'green'
;
default
:
return
'blue'
;
}
不要透過邏輯運算子來縮寫 if
陳述式:
foo
&&
bar
();
// no
if
(
foo
)
bar
();
// yes
foo
||
bar
();
// no
if
(
!
foo
)
bar
();
// yes
如果可能的話,將遞增運算子 (++
) 和遞減運算子 (--
) 用作陳述式;不要將它們用作運算式。在後者的情況下,它們會傳回一個值,而且雖然有一個助記符,你仍然需要思考才能找出正在發生什麼事:
// Unsure: what is happening?
return
++
foo
;
// Easy to understand
++
foo
;
return
foo
;
if
(
x
===
void
0
)
x
=
0
;
// not necessary in ES5
if
(
x
===
undefined
)
x
=
0
;
// preferable
從 ECMAScript 5 開始,第二種檢查方式較佳。變更未定義說明原因。
return
x
>>
0
;
// no
return
Math
.
round
(
x
);
// yes
位移運算子可用於將數字轉換為整數。不過,通常建議使用較明確的替代方案,例如 Math.round()
。轉換為整數提供所有轉換為整數方式的概觀。
有時您可以在 JavaScript 中使用巧妙手法,前提是該巧妙手法已成為既定模式。
如果您使用通用方法,您可以將 Object.prototype
縮寫為 {}
。下列兩個表達式等效:
Object
.
prototype
.
hasOwnProperty
.
call
(
obj
,
propKey
)
{}.
hasOwnProperty
.
call
(
obj
,
propKey
)
而且 Array.prototype
可以縮寫為 []
:
Array
.
prototype
.
slice
.
call
(
arguments
)
[].
slice
.
call
(
arguments
)
我對這個有點猶豫。這是一種技巧(您透過執行個體存取原型屬性)。但它可以減少雜亂,而且我預期引擎最終會最佳化此模式。
物件文字中的尾隨逗號在 ECMAScript 5 中是合法的:
var
obj
=
{
first
:
'Jane'
,
last
:
'Doe'
,
// legal: trailing comma
};
讓我們來看看一些我喜歡的慣例,這些慣例有點有爭議。
我們將從語法慣例開始
我喜歡相對緊湊的空白。範例是書面英文:開括號後和閉括號前沒有空白。逗號後有空白:
var
result
=
foo
(
'a'
,
'b'
);
var
arr
=
[
1
,
2
,
3
];
if
(
flag
)
{
...
}
對於匿名函式,我遵循 Douglas Crockford 的規則,在關鍵字 function
後加上空白。其理由是,如果您移除名稱,這就是命名函式表達式的樣子
function
foo
(
arg
)
{
...
}
// named function expression
function
(
arg
)
{
...
}
// anonymous function expression
這樣有助於閱讀,因為可以更輕鬆地找出運算子的範圍:
return
result
?
result
:
theDefault
;
// no
return
(
result
?
result
:
theDefault
);
// yes
接下來,我將說明變數的慣例
// no
var
foo
=
3
,
bar
=
2
,
baz
;
// yes
var
foo
=
3
;
var
bar
=
2
;
var
baz
;
這種方法的優點在於刪除、插入和重新排列行比較簡單,而且行會自動正確縮排。
var
宣告是區塊範圍。換句話說,您可以在使用變數的內容中宣告變數(在迴圈內、在then
區塊或else
區塊內,等等)。這種區域封裝讓程式碼片段在孤立狀態下更容易理解。移除程式碼片段或將它移到其他地方也比較容易。
作為前一條規則的附錄:不要在兩個不同的區塊中宣告同一個變數兩次。例如
// Don’t do this
if
(
v
)
{
var
x
=
v
;
}
else
{
var
x
=
10
;
}
doSomethingWith
(
x
);
前述程式碼的效果和用意與下列程式碼相同,因此應該以這種方式撰寫
var
x
;
if
(
v
)
{
x
=
v
;
}
else
{
x
=
10
;
}
doSomethingWith
(
x
);
現在我們將說明與物件導向相關的慣例。
我建議您
new
。
這樣做的主要優點是
對於建構函式,使用嚴格模式非常重要,因為它可以防止您忘記在實例化時使用new
運算子。而且您應該知道,您可以在建構函式中傳回任何物件。在實作建構函式的秘訣中提到了更多使用建構函式的秘訣。
我發現這樣的建構函式呼叫使用括號看起來更簡潔
var
foo
=
new
Foo
;
// no
var
foo
=
new
Foo
();
// yes
使用括號,讓兩個運算子不會互相競爭,結果不一定會是您預期的:
> false && true || true true > false && (true || true) false > (false && true) || true true
instanceof
特別棘手
> ! {} instanceof Array false > (!{}) instanceof Array false > !({} instanceof Array) true
不過,我發現建構函式後的函式呼叫沒有問題
new
Foo
().
bar
().
baz
();
// ok
(
new
Foo
()).
bar
().
baz
();
// not necessary
此區段收集各種提示
透過 Boolean
、Number
、String()
、Object()
(用作函式,切勿將這些函式用作建構函式)強制轉換值成某個類型。原因是此慣例更具描述性:
> +'123' // no 123 > Number('123') // yes 123 > ''+true // no 'true' > String(true) // yes 'true'
this
當成隱含參數
this
應僅指目前函式呼叫的接收者;不應將其濫用為隱含參數。原因是這些函式較容易呼叫和理解。我也喜歡將物件導向和函式機制分開:
// Avoid:
function
handler
()
{
this
.
logError
(...);
}
// Prefer:
function
handler
(
context
)
{
context
.
logError
(...);
}
in
和 hasOwnProperty
檢查屬性是否存在(請參閱屬性的反覆運算和偵測)
這比與 undefined
比較或檢查真值更能說明問題且更安全
// All properties:
if
(
obj
.
foo
)
// no
if
(
obj
.
foo
!==
undefined
)
// no
if
(
'foo'
in
obj
)
...
// yes
// Own properties:
if
(
obj
.
hasOwnProperty
(
'foo'
))
...
// risky for arbitrary objects
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
obj
,
'foo'
))
...
// safe
每當您考慮樣式問題時,請自問:什麼讓我的程式碼更容易理解?抵擋住耍小聰明的誘惑,並將大部分的機械式小聰明留給 JavaScript 引擎和縮小程式(請參閱第 32 章)。