Google的Javascript风格指南
7 JSDoc
JSDoc在所有类、域以及方法中被使用。
7.1 通用形式
在此示例中可以看到JSDoc块的基本格式:
/**
* Multiple lines of JSDoc text are written here,
* wrapped normally.
* @param {number} arg A number to do something to.
*/
function doSomething(arg) { … }
或者在此单行示例中:
/** @const @private {!Foo} A short bit of JSDoc. */
this.foo_ = foo;
如果单行注释溢出到多行,则必须在自己的行上使用带有 / ** 和 */ 的多行样式。
许多工具从JSDoc注释中提取元数据以执行代码验证和优化。 因此,这些注释必须格式正确。
7.2 Markdown
JSDoc用Markdown编写,尽管必要时它可能包括HTML。
请注意,自动提取JSDoc的工具(例如JsDossier)通常会忽略纯文本格式,因此如果您这样做:
/**
* Computes weight based on three factors:
* items sent
* items received
* last timestamp
*/
那么它会像这样出来:
Computes weight based on three factors: items sent items received last timestamp
相反,以如下的方式写Markdown的列表:
/**
* Computes weight based on three factors:
*
* - items sent
* - items received
* - last timestamp
*/
7.3 JSDoc标签
Google样式允许使用JSDoc标签的子集。 有关完整列表,请参见9.1 JSDoc标签参考。 大多数标签必须占据自己的行,并且标签应位于行的开头。
不允许:
/**
* The "param" tag must occupy its own line and may not be combined.
* @param {number} left @param {number} right
*/
function add(left, right) { ... }
不需要任何其他数据的简单标签(例如@ private,@ const,@ final,@ export)可以与同一行以及适当的可选类型组合到同一行上。
/**
* Place more complex annotations (like "implements" and "template")
* on their own lines. Multiple simple tags (like "export" and "final")
* may be combined in one line.
* @export @final
* @implements {Iterable<TYPE>}
* @template TYPE
*/
class MyClass {
/**
* @param {!ObjType} obj Some object.
* @param {number=} num An optional number.
*/
constructor(obj, num = 42) {
/** @private @const {!Array<!ObjType|number>} */
this.data_ = [obj, num];
}
}
对于何时组合标签或顺序不存在硬性规定,但是要保持一致。
有关在JavaScript中注释类型的常规信息,请参见Annotating JavaScript for the Closure Compiler和Types in the Closure Type System。
7.4 换行
行包装的块标签缩进了四个空格。 包装好的描述文字可能与前几行的描述对齐,但是不建议这种水平对齐方式。
/**
* Illustrates line wrapping for long param/return descriptions.
* @param {string} foo This is a param with a description too long to fit in
* one line.
* @return {number} This returns something that has a description too long to
* fit in one line.
*/
exports.method = function(foo) {
return 5;
};
包装@desc或@fileoverview描述时,请勿缩进。
7.5 顶级/文件级注释
文件可能具有顶级文件概述。 版权声明,作者信息和默认可见性级别是可选的。 通常,只要文件包含多个类定义,通常建议使用文件概述。 顶层注释旨在使不熟悉该代码的读者适应此文件中的内容。 如果存在,它可以提供文件内容以及任何依赖关系或兼容性信息的描述。 包装的线不缩进。
例:
/**
* @fileoverview Description of file, its uses and information
* about its dependencies.
* @package
*/
7.6 类注释
类,接口和记录必须带有描述和任何模板参数,已实现的接口,可见性或其他适当的标签,以文档形式记录。 类说明应为读者提供足够的信息,以了解如何以及何时使用该类,以及正确使用该类所需的任何其他注意事项。 构造函数上可能会省略文字说明。 @constructor和@extends批注不能与class关键字一起使用,除非该类用于声明@interface或扩展了通用类。
/**
* A fancier event target that does cool things.
* @implements {Iterable<string>}
*/
class MyFancyTarget extends EventTarget {
/**
* @param {string} arg1 An argument that makes this more interesting.
* @param {!Array<number>} arg2 List of numbers to be processed.
*/
constructor(arg1, arg2) {
// ...
}
};
/**
* Records are also helpful.
* @extends {Iterator<TYPE>}
* @record
* @template TYPE
*/
class Listable {
/** @return {TYPE} The next item in line to be returned. */
next() {}
}
7.7 Enum和typedef注释
所有枚举和typedef必须在上一行用适当的JSDoc标记(@typedef或@enum)进行记录。 公共枚举和typedef也必须有描述。 单个枚举项可能在上一行带有JSDoc注释的文档中。
/**
* A useful type union, which is reused often.
* @typedef {!Bandersnatch|!BandersnatchType}
*/
let CoolUnionType;
/**
* Types of bandersnatches.
* @enum {string}
*/
const BandersnatchType = {
/** This kind is really frumious. */
FRUMIOUS: 'frumious',
/** The less-frumious kind. */
MANXOME: 'manxome',
};
Typedef对于定义短记录类型或联合,复杂函数或通用类型的别名很有用。 对于具有许多字段的记录类型,应避免使用Typedef,因为它们不允许记录单个字段,也不允许使用模板或递归引用。 对于大记录类型,请使用@record。
7.8 方法和函数注释
在方法和命名函数中,必须记录参数和返回类型,除非相同签名@overrides省略所有类型。必要时应记录此类型。如果函数没有非空的return语句,则可以省略返回类型。
如果方法,参数和返回描述(但不是类型)在方法的其余JSDoc或其签名中显而易见,则可以省略。
方法描述以动词短语开头,该动词短语描述方法的作用。这个短语不是命令性的句子,而是用第三人称写的,好像在它之前有一个隐含的This方法。
如果方法覆盖超类方法,则它必须包含@override注释。覆盖的方法从超类方法继承所有JSDoc注释(包括可见性注释),并且应在覆盖的方法中将其省略。但是,如果在类型注释中完善了任何类型,则必须显式指定所有@param和@return注释。
/** A class that does something. */
class SomeClass extends SomeBaseClass {
/**
* Operates on an instance of MyClass and returns something.
* @param {!MyClass} obj An object that for some reason needs detailed
* explanation that spans multiple lines.
* @param {!OtherClass} obviousOtherClass
* @return {boolean} Whether something occurred.
*/
someMethod(obj, obviousOtherClass) { ... }
/** @override */
overriddenMethod(param) { ... }
}
/**
* Demonstrates how top-level functions follow the same rules. This one
* makes an array.
* @param {TYPE} arg
* @return {!Array<TYPE>}
* @template TYPE
*/
function makeArray(arg) { ... }
如果只需要记录函数的参数和返回类型,则可以选择在函数的签名中使用内联JSDocs。 这些内联JSDocs指定不带标签的return和param类型。
function /** string */ foo(/** number */ arg) {...}
如果需要描述或标签,请在方法上方使用单个JSDoc注释。 例如,返回值的方法需要@return标记。
class MyClass {
/**
* @param {number} arg
* @return {string}
*/
bar(arg) {...}
}
// Illegal inline JSDocs.
class MyClass {
/** @return {string} */ foo() {...}
}
/** Function description. */ bar() {...}
在匿名函数中,注释通常是可选的。 如果自动类型推断不足或显式注释提高了可读性,则对param进行注释并返回如下类型:
promise.then(
/** @return {string} */
(/** !Array<string> */ items) => {
doSomethingWith(items);
return items[0];
});
对于函数类型的表达式,请参见7.10.4 函数类型表达式。
7.9 属性注释
属性类型必须记录在案。 如果名称和类型提供了足够的文档来理解代码,则可以省略私有属性的描述。
公开导出的常量与属性的注释方式相同。
/** My class. */
class MyClass {
/** @param {string=} someString */
constructor(someString = 'default string') {
/** @private @const {string} */
this.someString_ = someString;
/** @private @const {!OtherType} */
this.someOtherThing_ = functionThatReturnsAThing();
/**
* Maximum number of things per pane.
* @type {number}
*/
this.someProperty = 4;
}
}
/**
* The number of times we'll try before giving up.
* @const {number}
*/
MyClass.RETRY_COUNT = 33;
7.10 Type注释
类型注释位于@ param,@ return,@ this和@type标记上,还可以在@ const,@ export和任何可见性标记上找到。 附加到JSDoc标记的类型注释必须始终用大括号括起来。
7.10.1 可空性
类型系统定义修饰符! 和? 分别用于非null和nullable。 这些修饰符必须在类型之前。
可空性修饰符对不同类型有不同的要求,分为两大类:
- 原语(字符串,数字,布尔值,符号,未定义,null)和文字({function(…):…}和{{foo:string …}})的类型注释始终默认不可为空。 使用 ? 修饰符使其可为空,但省略多余的!。
- 引用类型(通常,UpperCamelCase中的任何内容,包括some.namespace.ReferenceType)都引用在其他地方定义的类,枚举,记录或typedef。 由于这些类型可能为空,也可能不会为空,因此无法仅通过名称来判断它是否可为空。 始终使用显式? 和! 这些类型的修饰符,以防止在使用现场产生歧义。
不好的示例:
const /** MyObject */ myObject = null; // Non-primitive types must be annotated.
const /** !number */ someNum = 5; // Primitives are non-nullable by default.
const /** number? */ someNullableNum = null; // ? should precede the type.
const /** !{foo: string, bar: number} */ record = ...; // Already non-nullable.
const /** MyTypeDef */ def = ...; // Not sure if MyTypeDef is nullable.
// Not sure if object (nullable), enum (non-nullable, unless otherwise
// specified), or typedef (depends on definition).
const /** SomeCamelCaseName */ n = ...;
好的示例:
const /** ?MyObject */ myObject = null;
const /** number */ someNum = 5;
const /** ?number */ someNullableNum = null;
const /** {foo: string, bar: number} */ record = ...;
const /** !MyTypeDef */ def = ...;
const /** ?SomeCamelCaseName */ n = ...;
7.10.2 类型转换
如果编译器无法准确推断出表达式的类型,而goog.asserts中的断言函数无法对其进行补救,则可以通过添加类型注释并将表达式括在括号中来加紧类型。 请注意,括号是必需的。
/** @type {number} */ (x)
7.10.3 模板参数类型
始终指定模板参数。 这样,编译器可以做得更好,并使读者更容易理解代码的作用。
不推荐:
const /** !Object */ users = {};
const /** !Array */ books = [];
const /** !Promise */ response = ...;
推荐:
const /** !Object<string, !User> */ users = {};
const /** !Array<string> */ books = [];
const /** !Promise<!Response> */ response = ...;
const /** !Promise<undefined> */ thisPromiseReturnsNothingButParameterIsStillUseful = ...;
const /** !Object<string, *> */ mapOfEverything = {};
不应使用模板参数的情况:
- 对象用于类型层次结构,而不是类似map的结构。
7.10.4 函数类型表达式
术语注:函数类型表达式是指函数类型的类型注释,注释中带有关键字function(请参见下面的示例)。
在给出函数定义的地方,请勿使用函数类型表达式。 使用@param和@return或内联注释指定参数和返回类型(请参见7.8 方法和函数注释)。 这包括匿名函数和已定义并分配给const的函数(其中jsdoc函数出现在整个赋值表达式上方)。
函数类型表达式是必需的,例如,在@ typedef,@ param或@return内部。 如果未立即使用函数定义对其进行初始化,则也可将其用于函数类型的变量或属性。
/** @private {function(string): string} */
this.idGenerator_ = googFunctions.identity;
使用函数类型表达式时,请始终明确指定返回类型。 否则,默认的返回类型是未知的(?),这会导致奇怪和意外的行为,并且很少是实际需要的。
坏习惯-类型错误,但未给出警告:
/** @param {function()} generateNumber */
function foo(generateNumber) {
const /** number */ x = generateNumber(); // No compile-time type error here.
}
foo(() => 'clearly not a number');
好习惯:
/**
* @param {function(): *} inputFunction1 Can return any type.
* @param {function(): undefined} inputFunction2 Definitely doesn't return
* anything.
* NOTE: the return type of `foo` itself is safely implied to be {undefined}.
*/
function foo(inputFunction1, inputFunction2) {...}
7.10.5 空格
在类型注释中,每个逗号或冒号后需要一个空格或换行符。 可以插入其他换行符以提高可读性或避免超出列限制。 这些中断应按照适用的准则进行选择和缩进(例如4.5 换行和4.2 块缩进:+2个空格)。 类型注释中不允许使用其他空格。
好习惯:
/** @type {function(string): number} */
/** @type {{foo: number, bar: number}} */
/** @type {number|string} */
/** @type {!Object<string, string>} */
/** @type {function(this: Object<string, string>, number): string} */
/**
* @type {function(
* !SuperDuperReallyReallyLongTypedefThatForcesTheLineBreak,
* !OtherVeryLongTypedef): string}
*/
/**
* @type {!SuperDuperReallyReallyLongTypedefThatForcesTheLineBreak|
* !OtherVeryLongTypedef}
*/
坏习惯:
// Only put a space after the colon
/** @type {function(string) : number} */
// Put spaces after colons and commas
/** @type {{foo:number,bar:number}} */
// No space in union types
/** @type {number | string} */
7.11 可见性注释
可见性注释(@ private,@ package,@ protected)可以在@fileoverview块或任何导出的符号或属性中指定。 不要为函数或模块的顶层指定局部变量的可见性。 所有@private名称必须以下划线结尾。