JavaScript 有很多很棒的代码风格指南。因此,没有必要再写一个。相反,本章描述了元代码风格规则,并调查了现有的代码风格指南和已建立的最佳实践。它还提到了我喜欢的、更具争议性的实践。其理念是补充现有的代码风格指南,而不是取代它们。
以下是我喜欢的代码风格指南:
此外,还有两个元代码风格指南
本节将介绍一些通用的代码编写技巧。
var
语句第二条规则是,如果你加入了一个现有项目,你应该严格遵守它的规则(即使你不同意)。
对于大多数代码来说,阅读代码的时间远远大于编写代码的时间。因此,尽可能简化阅读过程非常重要。以下是一些实现此目标的准则
redBalloon
比 rdBlln
更容易阅读。大多数代码库都充满了新的想法和概念。这意味着,如果你想使用一个代码库,你需要学习这些想法和概念。与教科书相比,代码的另一个挑战是,人们不会线性地阅读它。他们会从任何地方跳进去,并且应该能够大致理解发生了什么。代码库的三个部分会有所帮助
很多聪明才智都集中在这些优化上。然而,你通常不需要它们。一方面,JavaScript 引擎正变得越来越智能,并自动优化遵循既定模式的代码的速度。另一方面,代码压缩工具(第 32 章)会重写你的代码,使其尽可能小。在这两种情况下,工具都为你提供了便利,因此你无需自己动手。
有时,你不得不优化代码的性能。如果你这样做,一定要测量和优化正确的部分。在浏览器中,问题通常与 DOM 和 HTML 相关,而不是与语言本身相关。
大多数 JavaScript程序员都同意以下最佳实践:
始终使用严格相等 (===
) 和严格不等 (!==
)。我建议永远不要违反这条规则。我甚至更喜欢以下两个条件中的第一个,即使它们是等效的:
if
(
x
!==
undefined
&&
x
!==
null
)
...
// my choice
if
(
x
!=
null
)
...
// equivalent
在大括号分隔代码块的语言中,大括号风格决定了大括号的位置。在类 C 语言(如 Java 和 JavaScript)中,两种大括号风格最常见:Allman 风格和 1TBS。
如果一个语句包含一个块,那么该块被认为与语句的头部 somewhat separate:它的左大括号位于单独的一行中,与头部处于相同的缩进级别。例如:
// 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 开始,第二种检查方式更好。更改 undefined解释了原因。
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
)
我对这一点持矛盾的态度。这是一种 hack(你正在通过实例访问原型属性)。但它减少了混乱,我希望引擎最终能优化这种模式。
对象字面量中的尾随逗号在 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 章)。