Google的Javascript风格指南
4 格式化
术语注释:类块构造是指类,函数,方法或用大括号分隔的代码块的主体。 请注意,通过5.2 数组文法和5.3 对象文法,可以将任何数组或对象文法视作一个类似于块的构造。
提示:使用clang格式。 JavaScript社区投入了很多精力来确保clang格式对JavaScript文件执行正确的操作。 clang格式已与几种流行的编辑器集成。
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语句可以完全放在一行中而没有换行(并且没有其它),当它提高可读性时,可以放在一行中而没有大括号。 这是控件结构可能省略花括号和换行符的唯一情况。
if (shortCondition()) foo();
4.1.2 非空块的K&R风格
对于非空块或者类似块,大括弧遵循Kernighan和Ritchie style (Egyptian brackets) :
- 左括弧之前没有换行。
- 左括弧之后换行。
- 右括弧前有换行。
- 如果右括号终止一个语句或函数或类语句的主体或类方法,则在右括号后换行。 具体来说,如果在括号之后紧跟着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个空格
每次打开新的块或类似块的构造时,缩进量都会增加2个空格。 当块结束时,缩进将返回到先前的缩进级别。 缩进级别适用于整个块中的代码和注释。 (请参见4.1.2 非空块的K&R风格)。
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 对象文法:可类似于块
可以选择格式化任何对象文字,就好像它是“类似块的构造”一样。 相同的示例适用于4.2.1 数组文法:可类似于块。例如,以下全部有效(并非详尽列表):
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 类文法
类文字(无论是声明还是表达式)都缩进为块。 不要在方法之后或类声明的右括号后添加分号(包含类表达式的语句(如赋值)仍以分号终止)。 除非该类扩展了模板化类型,否则请使用extend关键字,而不要使用@extends JSDoc注释。
示例:
class Foo {
constructor() {
/** @type {number} */
this.x = 42;
}
/** @return {number} */
method() {
return this.x;
}
}
Foo.Empty = class {};
/** @extends {Foo<string>} */
foo.Bar = class extends Foo {
/** @override */
method() {
return super.method() / 2;
}
};
/** @interface */
class Frobnicator {
/** @param {string} message */
frobnicate(message) {}
}
4.2.4 函数表达式
在函数调用的参数列表中声明匿名函数时,函数的主体缩进比前面的缩进深度多2个空格。
示例:
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语句
与其他任何块一样,切换块的内容缩进+2。
在开关标签之后,出现换行符,并且压痕级别增加了+2,就像在打开一个块一样。 如果词汇作用域需要,可以使用显式块。 以下开关标签将返回到先前的缩进级别,就好像一个块已被关闭一样。
在中断和以下情况之间,空行是可选的。例:
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 分号是必须的
每个语句必须以分号结尾。禁止依靠自动分号插入。
4.4 列数限制:80
Javascript代码的列数限制为80个字符。除非另有说明,否则超出此限制的任何行都必须进行换行,如4.5 换行中所述。
例外:
- goog.module,goog.require和goog.requireType语句(请参见3.3 goog.module语句和3.6 goog.require和goog.requireType语句)。
- ES模块从语句导入和导出(请参加3.4.1 导入 和3.4.2.4 export from语句)。
- 不能遵守列限制或者妨碍列数限制的例子包括:
- 需要在源中点击的一个长URL;
- 用户复制和粘贴的shell命令;
- 可能需要完全复制或者搜索的长字符串文字(例如长文件路径)。
4.5 换行
术语注释:换行将代码块分成多行以遵守列数限制,否则该代码块可以合法地容纳在一行中。
没有全面的确定性公式可以准确显示在每种情况下的换行方式。 通常,有几种有效的方法可以对同一段代码进行换行。
提示:提取方法或局部变量可以解决问题,而无需换行。
4.5.1 在哪里做行中断
换行的主要指令是:更倾向于在更高的语法级别上做行中断。
倾向于:
currentEstimate =
calc(currentEstimate + x * currentEstimate) /
2.0;
而不是:
currentEstimate = calc(currentEstimate + x *
currentEstimate) / 2.0;
在前面的示例中,从最高到最低的语法级别如下:赋值,除法,函数调用,参数,数字常数。
运算符的包装如下:
- 当一行在一个操作符处结束时,在该操作符之后中断。 (请注意,这与Java的Google风格不同)
- 这不适用于实际上不是运算符的点(.)。 - 方法或构造函数名称保持附加在其后的括号(()中。
- 逗号(,)始终附加在其前面的令牌上。
注意:换行的主要目的是要有清晰的代码,而不必是适合最少行数的代码。
4.5.2 缩进连续线至少+4个空格
换行时,第一行(每条连续行)之后的每一行都应从原始行开始缩进至少+4,除非它属于块缩进规则。
当有多个连续行时,缩进可以适当地更改为超过+4。 通常,在更深的句法级别上的连续行以4的较大倍数缩进,并且当且仅当它们以句法上平行的元素开始时,两条行才使用相同的缩进级别。
4.6.3 水平对齐:不鼓励:不鼓励使用不固定数量的空格将某些标记与前几行对齐。
4.6 空格
4.6.1 垂直空格
出现一个空白行:
- 在类或对象文字中的连续方法之间
- 例外:对象文字中两个连续属性定义之间的空白行(它们之间没有其他代码)是可选的。 根据需要使用此类空行来创建字段的逻辑分组。
- 在方法主体内,谨慎地创建语句的逻辑分组。 在功能主体的开头或结尾处不允许有空行。
- 可选地,在类或对象文字中的第一个方法或最后一个方法之后(既不支持也不反对)。
- 根据本文档其他部分的要求(例如3.6 goog.require和goog.requireType语句)。
允许使用多个连续的空行,但从不要求(也不鼓励)。
4.6.2 水平空格
水平空白的使用取决于位置,分为三大类:前导(在行的开头),尾随(在行的结尾)和内部。 前导空格(即缩进)在其他地方介绍。 禁止尾随空格。
除了语言或其他样式规则的要求之外,除了文字,注释和JSDoc外,单个内部ASCII空间也仅出现在以下位置。
- 将函数和超级除外的所有保留字(例如if,for或catch)与该行后跟的开放括号(())分开。
- 将任何保留字(例如else或catch)与该行之前的右花括号(})分开。
- 在任何大括号({)之前,但有两个例外:
- 在对象常量之前,该对象常量是函数的第一个参数或数组常量中的第一个元素(例如foo({a:[{c:d}]})。
- 在模板扩展中,如语言所禁止(例如有效:
ab $ {1 + 2} cd
,无效:xy $ {3} z
)。
- 在任何二元或三元运算符的两侧。
- 在逗号(,)或分号(;)之后。请注意,这些字符之前绝对不允许有空格。
- 对象文字中冒号(:)之后。
- 在双斜杠(//)的两边,开始行尾注释。在此,允许多个空格,但不是必需的。
- 在开放块注释字符之后和在封闭字符的两侧(例如,用于简短形式的类型声明,强制类型转换和参数名称注释):
this.foo = / ** @type {number} * /(bar);或函数(/ *字符串 / foo){;或baz(/ * buzz = * / true))。
4.6.3 水平对齐:不鼓励
术语注:水平对齐是在代码中添加可变数量的附加空格的一种做法,目的是使某些标记直接出现在前几行中其他标记的下面。
允许这种做法,但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则另外在7 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 */);