4 格式化
术语说明:*块状结构(block-like construct)*指类、函数、方法的主体或花括号分隔的代码块。请注意,根据 ?? 和 ??,任何数组或对象字面量都可以选择性地被视为块状结构。
提示:使用 clang-format。JavaScript 社区已经投入精力确保 clang-format
在 JavaScript 文件上”做正确的事情”。clang-format
已与多个流行编辑器集成。
4.1 花括号
4.1.1 所有控制结构都使用花括号
所有控制结构(即 if、else、for、do、while
以及其他所有控制结构)都需要使用花括号,即使主体只包含一条语句。非空块的第一条语句必须从自己的一行开始。
不允许:
if (someVeryLongCondition())
doSomething();
for (let i = 0; i < foo.length; i++) bar(foo[i]);例外:一个简单的 if 语句,如果可以完全放在一行中而无需换行(并且没有
else),则可以保持在一行中而不使用花括号,前提是这样做能提高可读性。这是控制结构可以省略花括号和换行的唯一情况。
if (shortCondition()) foo();4.1.2 非空块:K&R 风格
对于非空块和块状结构,花括号遵循 Kernighan 和 Ritchie 风格(“埃及括号 ”):
- 左花括号前不换行。
- 左花括号后换行。
- 右花括号前换行。
- 右花括号后换行,如果该花括号终止一个语句或函数、类语句或类方法的主体。具体来说,如果花括号后面跟着
else、catch、while、逗号、分号或右括号,则不换行。
示例:
class InnerClass {
constructor() {}
/** @param {number} foo */
method(foo) {
if (condition(foo)) {
try {
// Note: this might fail.
something();
} catch (err) {
recover();
}
}
}
}4.1.3 空块:可以简洁
空块或块状结构可以在打开后立即关闭,中间没有字符、空格或换行(即
{}),除非它是多块语句(直接包含多个块的语句:if/else 或
try/catch/finally)的一部分。
示例:
function doNothing() {}不允许:
if (condition) {
// …
} else if (otherCondition) {} else {
// …
}
try {
// …
} catch (e) {}4.2 块缩进:+2 个空格
每当打开一个新块或块状结构时,缩进增加两个空格。当块结束时,缩进返回到之前的缩进级别。缩进级别适用于整个块中的代码和注释。(参见 ?? 中的示例)。
4.2.1 数组字面量:可选的“块状”
任何数组字面量都可以选择性地像”块状结构”一样格式化。例如,以下都是有效的(不是穷举列表):
const a = [
0,
1,
2,
];
const b =
[0, 1, 2];const c = [0, 1, 2];
someMethod(foo, [
0, 1, 2,
], bar);允许其他组合,特别是在强调元素之间的语义分组时,但不应仅用于减少较大数组的垂直尺寸。
4.2.2 对象字面量:可选的“块状”
任何对象字面量都可以选择性地像”块状结构”一样格式化。与 ?? 相同的示例适用。例如,以下都是有效的(不是穷举列表):
const a = {
a: 0,
b: 1,
};
const b =
{a: 0, b: 1};const c = {a: 0, b: 1};
someMethod(foo, {
a: 0, b: 1,
}, bar);4.2.3 类字面量
类字面量(无论是声明还是表达式)都像块一样缩进。不要在方法后面或类声明的右花括号后面添加分号(包含类表达式的语句——例如赋值——仍然以分号结尾)。对于继承,extends
关键字就足够了,除非超类是模板化的。模板化类型的子类必须在 @extends
JSDoc 注解中显式指定模板类型,即使只是传递相同的模板名称。
示例:
/** @template T */
class Foo {
/** @param {T} x */
constructor(x) {
/** @type {T} */
this.x = x;
}
}
/** @extends {Foo<number>} */
class Bar extends Foo {
constructor() {
super(42);
}
}
exports.Baz = class extends Bar {
/** @return {number} */
method() {
return this.x;
}
};/** @extends {Bar} */ // <-- unnecessary @extends
exports.Baz = class extends Bar {
/** @return {number} */
method() {
return this.x;
}
};4.2.4 函数表达式
在函数调用的参数列表中声明匿名函数时,函数主体相对于前面的缩进深度多缩进两个空格。
示例:
prefix.something.reallyLongFunctionName('whatever', (a1, a2) => {
// Indent the function body +2 relative to indentation depth
// of the 'prefix' statement one line above.
if (a1.equals(a2)) {
someOtherLongFunctionName(a1);
} else {
andNowForSomethingCompletelyDifferent(a2.parrot);
}
});
some.reallyLongFunctionCall(arg1, arg2, arg3)
.thatsWrapped()
.then((result) => {
// Indent the function body +2 relative to the indentation depth
// of the '.then()' call.
if (result) {
result.use();
}
});4.2.5 Switch 语句
与任何其他块一样,switch 块的内容缩进 +2。
在 switch 标签之后,出现一个新行,缩进级别增加
+2,就像打开了一个块一样。如果词法作用域需要,可以使用显式块。下一个
switch 标签返回到之前的缩进级别,就像关闭了一个块一样。
break 和后续 case 之间的空行是可选的。
示例:
switch (animal) {
case Animal.BANDERSNATCH:
handleBandersnatch();
break;
case Animal.JABBERWOCK:
handleJabberwock();
break;
default:
throw new Error('Unknown animal');
}4.3 语句
4.3.1 每行一条语句
每条语句后面跟一个换行符。
4.3.2 必须使用分号
每条语句必须以分号结尾。禁止依赖自动分号插入(ASI)。
4.4 列限制:80
JavaScript 代码的列限制为 80 个字符。除以下注明的情况外,任何超过此限制的行都必须进行换行,如 ?? 中所述。
例外:
goog.module、goog.require和goog.requireType语句(参见 ?? 和 ??)。- ES 模块的
import和export from语句(参见 ?? 和 ??)。 - 无法遵守列限制或遵守列限制会妨碍可发现性的行。示例包括:
- 应在源代码中可点击的长 URL。
- 用于复制粘贴的 shell 命令。
- 可能需要完整复制或搜索的长字符串字面量(例如,长文件路径)。
4.5 换行
术语说明:*换行(Line wrapping)*是将一段代码拆分为多行以遵守列限制,而该代码段本来可以合法地放在一行中。
没有全面的、确定性的公式来展示在每种情况下确切如何换行。通常同一段代码有多种有效的换行方式。
注意:虽然换行的典型原因是避免超出列限制,但即使实际上可以放在列限制内的代码,也可以由作者自行决定进行换行。
提示:提取方法或局部变量可以在不需要换行的情况下解决问题。
4.5.1 在哪里断行
换行的首要准则是:优先在更高的语法级别处断行。
推荐:
currentEstimate =
calc(currentEstimate + x * currentEstimate) /
2.0;不推荐:
currentEstimate = calc(currentEstimate + x *
currentEstimate) / 2.0;在上面的示例中,语法级别从高到低依次为:赋值、除法、函数调用、参数、数字常量。
运算符的换行方式如下:
- 当一行在运算符处断开时,断点在符号之后。(请注意,这与 Google Java
风格中使用的做法不同。)
- 这不适用于“点”(
.),因为它实际上不是运算符。
- 这不适用于“点”(
- 方法或构造函数名称与其后面的左括号(
()保持在同一行。 - 逗号(
,)与其前面的标记保持在同一行。 return和返回值之间不得添加换行符,因为这会改变代码的含义。- 带有类型名称的 JSDoc
注解在
{之后断行。这是必要的,因为带有可选类型的注解(@const、@private、@param等)不会扫描下一行。
注意:换行的主要目标是拥有清晰的代码,而不一定是适合最少行数的代码。
4.5.2 续行至少缩进 +4 个空格
换行时,第一行之后的每一行(每个续行)从原始行开始至少缩进 +4,除非它属于块缩进规则。
当有多个续行时,缩进可以适当地超过 +4。通常,更深语法级别的续行以 4 的更大倍数缩进,并且两行使用相同的缩进级别,当且仅当它们以语法上平行的元素开头。
?? 讨论了使用可变数量的空格来将某些标记与前面的行对齐这一不推荐的做法。
4.6 空白
4.6.1 垂直空白
以下位置出现单个空行:
- 类或对象字面量中连续方法之间
- 例外:对象字面量中两个连续属性定义之间的空行(它们之间没有其他代码)是可选的。此类空行根据需要用于创建字段的逻辑分组。
- 在方法体内,少量使用以创建语句的逻辑分组。不允许在函数体的开头或结尾使用空行。
- 可选地在类或对象字面量中的第一个方法之前或最后一个方法之后(既不鼓励也不反对)。
- 按照本文档其他部分的要求(例如 ??)。
允许多个连续空行,但从不要求(也不鼓励)。
4.6.2 水平空白
水平空白的使用取决于位置,分为三大类:前导(在行首)、尾随(在行尾)和内部。前导空白(即缩进)在其他地方讨论。禁止使用尾随空白。
除了语言或其他风格规则要求的地方,以及除了字面量、注释和 JSDoc 之外,单个内部 ASCII 空格也仅出现在以下位置。
- 将任何保留字(如
if、for或catch)与该行中紧跟其后的左括号(()分隔,但function和super除外。 - 将任何保留字(如
else或catch)与该行中其前面的右花括号(})分隔。 - 在任何左花括号(
{)之前,有两个例外:- 当对象字面量是函数的第一个参数或数组字面量的第一个元素时(例如
foo({a: [{c: d}]}))。 - 在模板扩展中,因为语言禁止(例如,有效:
`ab${1 + 2}cd`,无效:`xy$ {3}z`)。
- 当对象字面量是函数的第一个参数或数组字面量的第一个元素时(例如
- 任何二元或三元运算符的两侧。
- 逗号(
,)或分号(;)之后。请注意,这些字符之前从不允许有空格。 - 对象字面量中冒号(
:)之后。 - 行尾注释开始的双斜杠(
//)的两侧。这里允许多个空格,但不是必需的。 - 块注释的开始字符之后和结束字符的两侧(例如,用于简短形式的类型声明、类型转换和参数名注释:
this.foo = /** @type {number} */ (bar);或function(/** string */ foo) {;或baz(/* buzz= */ true))。
4.6.3 水平对齐:不推荐
术语说明:*水平对齐(Horizontal alignment)*是在代码中添加可变数量的额外空格,目的是使某些标记直接出现在前面行中某些其他标记的下方。
这种做法是允许的,但 Google 风格通常不推荐。甚至不要求在已经使用水平对齐的地方保持水平对齐。
以下是一个没有对齐的示例,后面是一个有对齐的示例。两者都是允许的,但后者不推荐:
{
tiny: 42, // this is great
longer: 435, // this too
};
{
tiny: 42, // permitted, but future edits
longer: 435, // may leave it unaligned
};提示:对齐可以帮助可读性,但它给未来的维护带来了问题。考虑一个只需要修改一行的未来更改。这个更改可能会使以前令人愉快的格式变得凌乱,而这是允许的。更常见的情况是,它会促使编码者(也许是你)调整相邻行的空白,可能会触发一连串的重新格式化。这个一行的更改现在有了“爆炸半径”。这在最坏的情况下会导致无意义的忙碌工作,但在最好的情况下仍然会损坏版本历史信息、减慢审阅者的速度并加剧合并冲突。
4.6.4 函数参数
优先将所有函数参数放在与函数名称相同的行上。如果这样做会超过 80 列限制,则参数必须以可读的方式换行。为了节省空间,你可以尽可能接近 80 个字符进行换行,或者将每个参数放在自己的行上以增强可读性。缩进应为四个空格。允许对齐到括号,但不推荐。以下是最常见的参数换行模式:
// Arguments start on a new line, indented four spaces. Preferred when the
// arguments don't fit on the same line with the function name (or the keyword
// "function") but fit entirely on the second line. Works with very long
// function names, survives renaming without reindenting, low on space.
doSomething(
descriptiveArgumentOne, descriptiveArgumentTwo, descriptiveArgumentThree) {
// …
}
// If the argument list is longer, wrap at 80. Uses less vertical space,
// but violates the rectangle rule and is thus not recommended.
doSomething(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
// …
}
// Four-space, one argument per line. Works with long function names,
// survives renaming, and emphasizes each argument.
doSomething(
veryDescriptiveArgumentNumberOne,
veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy,
artichokeDescriptorAdapterIterator) {
// …
}4.7 分组括号:推荐使用
只有当作者和审阅者一致认为没有合理的可能性在没有括号的情况下代码会被误解,并且括号也不会使代码更易于阅读时,才可以省略可选的分组括号。假设每个读者都记住了整个运算符优先级表是不合理的。
不要在 delete、typeof、void、return、throw、case、in、of
或 yield 之后的整个表达式周围使用不必要的括号。
类型转换需要括号:/** @type {!Foo} */ (foo)。
4.8 注释
本节讨论实现注释。JSDoc 在 ?? 中单独讨论。
4.8.1 块注释风格
块注释与周围代码缩进在同一级别。它们可以使用 /* … */ 或 //
风格。对于多行 /* … */ 注释,后续行必须以与前一行 * 对齐的 *
开头,以使注释在没有额外上下文的情况下显而易见。
/*
* This is
* okay.
*/
// And so
// is this.
/* This is fine, too. */注释不应被用星号或其他字符绘制的框包围。
不要使用 JSDoc(/** … */)来编写实现注释。
4.8.2 参数名注释
“参数名”注释应在值和方法名不足以传达含义,且重构方法使其更清晰不可行时使用。它们的首选格式是在值之前带有 “=”:
someFunction(obviousParam, /* shouldRender= */ true, /* name= */ 'hello');为了与周围代码的一致性,你也可以将它们放在值之后,不带 “=”:
someFunction(obviousParam, true /* shouldRender */, 'hello' /* name */);