为 Closure 编译器添加 JavaScript 注解

注意:此页面已过期。如需查看完整列表,请访问 https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler

概览

Closure 编译器可以使用有关 JavaScript 变量的数据类型信息提供增强型优化和警告。但是,JavaScript 无法声明类型。

由于 JavaScript 没有用于声明变量类型的语法,因此您必须在代码中使用注释来指定数据类型。

Closure 编译器的类型语言派生自 JSDoc 文档生成工具使用的注解,但之后已相划分。它现在包含 JSDoc 不支持的多个注解,反之亦然。本文档介绍了 Closure 编译器能够理解的一组注解和类型表达式。

  1. JSDoc 代码
  2. 类型表达式
  3. 通用类型

JSDoc 标记

Closure 编译器在 JSDoc 标记中查找类型信息。请使用以下参考表中所述的 JSDoc 标记来帮助编译器优化代码,并检查其是否存在可能的类型错误和其他错误。

此表格仅包含影响 Closure 编译器行为的标记。如需了解其他 JSDoc 标记,请参阅 JSDoc 工具包文档

标记 说明
@abstract

将方法标记为抽象。与将方法设置为 goog.abstractMethod 类似,编译器可以删减带有 @abstract 注释的方法,以缩减代码大小。

如果带有 @abstract 标记的方法具有非空的实现,编译器就会生成警告。

例如:
/** @abstract */
foo.MyClass.prototype.abstractMethod = function() {};
@const

将变量标记为只读。编译器可以内嵌 @const 变量来优化 JavaScript 代码。

类型声明是可选的。

如果为标记了 @const 的变量赋值多次,编译器会生成警告。如果变量是对象,请注意,编译器不会禁止更改对象的属性。

例如:
/** @const */ var MY_BEER = 'stout';

/**
 * My namespace's favorite kind of beer.
 * @const {string}
 */
mynamespace.MY_BEER = 'stout';

/** @const */ MyClass.MY_BEER = 'stout';
@constructor

将函数标记为构造函数。 编译器需要为 new 关键字使用的任何函数添加 @constructor 注解。

例如:

/**
 * A rectangle.
 * @constructor
 */
function GM_Rect() {
  ...
}
@define 表示可在编译时由编译器替换的常量。 通过左侧示例,您可以将标记 --define='ENABLE_DEBUG=false' 传递给编译器,以将 ENABLE_DEBUG 的值更改为 false。定义的常量的类型可以是数字、字符串或布尔值。定义只能在全局范围内使用。

例如:

/** @define {boolean} */
var ENABLE_DEBUG = true;

/** @define {boolean} */
goog.userAgent.ASSUME_IE = false;
@deprecated

标记函数、方法或属性,以便使用时会生成编译器警告,指出不应再使用。

例如:

/**
 * Determines whether a node is a field.
 * @return {boolean} True if the contents of
 *     the element are editable, but the element
 *     itself is not.
 * @deprecated Use isField().
 */
BN_EditUtil.isTopEditableField = function(node) {
  ...
};
@dict

@dict 用于创建具有可变数量的属性的对象。使用 @dict 为构造函数(在此示例中为 Foo)添加注解时,您只能使用括号表示法来访问 Foo 对象的属性。该注解也可以直接用于对象字面量。

例如:

/**
 * @constructor
 * @dict
 */
function Foo() {}
var obj1 = new Foo();
obj1['x'] = 123;
obj1.x = 234;  // warning

var obj2 = /** @dict */ { 'x': 321 };
obj2.x = 123;  // warning
@enum

指定枚举的类型。枚举是一个对象,其属性构成了一组相关常量。@enum 标记必须后跟类型表达式

枚举的类型标签适用于该枚举的每个属性。例如,如果枚举的类型为 number,则其枚举属性必须为数字。如果省略枚举的类型,则系统会假定为 number

例如:

/**
 * Enum for tri-state values.
 * @enum {number}
 */
project.TriState = {
  TRUE: 1,
  FALSE: -1,
  MAYBE: 0
};
@export

提供此代码

/** @export */
foo.MyPublicClass.prototype.myPublicMethod = function() {
  // ...
};

当编译器使用 --generate_exports 标记运行时,将会生成代码:

goog.exportProperty(foo.MyPublicClass.prototype, 'myPublicMethod',
  foo.MyPublicClass.prototype.myPublicMethod);

这会将符号导出到未编译的代码。您可以将 /** @export {SomeType} */ 作为

/**
 * @export
 * @type {SomeType}
 */
的简写形式

使用 @export 注解的代码必须符合以下条件之一:

  1. 包含 closure/base.js,或
  2. 在自己的代码库中定义具有相同方法签名的 goog.exportSymbolgoog.exportProperty
@extends

将类或接口标记为从其他类继承。标有 @extends 的类还必须标有 @constructor@interface

注意:@extends 不会导致一个类从另一个类继承。该注解只是告诉编译器可以在检查类型时将一个类视为另一个类的子类。

如需查看继承的示例实现,请参阅 Closure 库函数 goog.inherits()

例如:

/**
 * Immutable empty node list.
 * @constructor
 * @extends {goog.ds.BasicNodeList}
 */
goog.ds.EmptyNodeList = function() {
  ...
};
@final

表示此类不可扩展。 对于方法,表示不允许任何子类替换该方法。

例如:

/**
 * A class that cannot be extended.
 * @final
 * @constructor
 */
sloth.MyFinalClass = function() { ... }

/**
 * A method that cannot be overridden.
 * @final
 */
sloth.MyFinalClass.prototype.method = function() { ... };
@implements

@constructor 一起使用,以指明类实现了接口。

如果您使用 @implements 标记构造函数,但未能实现接口定义的所有方法和属性,编译器会生成警告。

例如:


/**
 * A shape.
 * @interface
 */
function Shape() {};
Shape.prototype.draw = function() {};

/**
 * @constructor
 * @implements {Shape}
 */
function Square() {};
Square.prototype.draw = function() {
  ...
};
@implicitCast

此注解只能显示在 extern 属性声明中。该属性具有声明的类型,但您可以为它分配任何类型,而不显示警告。访问该属性时,您会获得所声明类型的值。例如,可以为 element.innerHTML 分配任何类型,但始终会返回字符串。

/**
 * @type {string}
 * @implicitCast
 */
Element.prototype.innerHTML;
@inheritDoc

表示子类的方法或属性有意隐藏父类的方法或属性,并且具有完全相同的文档。请注意,@inheritDoc 标记表示 @override 标记。

例如:

/** @inheritDoc */
project.SubClass.prototype.toString = function() {
  ...
};
@interface

将函数标记为接口。接口可指定某个类型的必需成员。任何实现接口的类都必须实现在该接口的原型上定义的所有方法和属性。请参阅 @implements

编译器验证接口是否未实例化。如果 new 关键字与接口函数一起使用,编译器会生成警告。

例如:

/**
 * A shape.
 * @interface
 */
function Shape() {};
Shape.prototype.draw = function() {};

/**
 * A polygon.
 * @interface
 * @extends {Shape}
 */
function Polygon() {};
Polygon.prototype.getSides = function() {};
@lends

表示对象字面量的键应被视为其他对象的属性。此注释应仅出现在对象字面量中。

请注意,大括号中的名称不是其他注解中的类型名称。它是对象名称。它会为属性所绑定的对象命名。 例如,@type {Foo} 表示“实例 Foo”,但 @lends {Foo} 表示“构造函数 Foo”。

如需详细了解此注解,请参阅 JSDoc 工具包文档

例如:

goog.object.extend(
    Button.prototype,
    /** @lends {Button.prototype} */ ({
      isButton: function() { return true; }
    }));
@license@preserve

告知编译器在标记文件的已编译代码之前插入关联的注释。此注解允许重要声明(例如法律许可或版权文本)在编译期间保持不变。系统会保留换行符。

例如:

/**
 * @preserve Copyright 2009 SomeThirdParty.
 * Here is the full license text and copyright
 * notice for this file. Note that the notice can span several
 * lines and is only terminated by the closing star and slash:
 */
@nocollapse

表示不应由编译器收起为变量的属性。@nocollapse 的主要用途是允许导出可变属性。请注意,编译器仍可重命名非收起属性。如果您使用 @nocollapse 为作为对象的对象添加注解,则其所有属性也将保持原样。

例如:

/**
 * A namespace.
 * @const
 */
var foo = {};

/**
 * @nocollapse
 */
foo.bar = 42;

window['foobar'] = foo.bar;
@nosideeffects

表示对声明的外部函数的调用没有副作用。 此注解允许编译器在未使用返回值时移除对函数的调用。该注释只能在 extern files 中使用。

例如:

/** @nosideeffects */
function noSideEffectsFn1() {}

/** @nosideeffects */
var noSideEffectsFn2 = function() {};

/** @nosideeffects */
a.prototype.noSideEffectsFn3 = function() {};
@override

表示子类的方法或属性有意隐藏父类的方法或属性。如果不包含其他注解,则方法或属性会自动从其父类继承注解。

例如:

/**
 * @return {string} Human-readable representation of
 *     project.SubClass.
 * @override
 */
project.SubClass.prototype.toString = function() {
  ...
};
@package

将成员或属性标记为私有。只有同一目录中的代码才能访问标记为 @package 的名称。特别是,父级目录和子级目录中的代码无法访问标记为 @package 的名称。

公共构造函数可以具有 @package 属性,以限制目录之外的调用方可以使用的方法。另一方面,@package 构造函数可以具有公共属性,以防止目录外的调用方直接实例化类型。

例如:

/**
 * Returns the window object the foreign document resides in.
 *
 * @return {Object} The window object of the peer.
 * @package
 */
goog.net.xpc.CrossPageChannel.prototype.getPeerWindowObject = function() {
  // ...
};
@param

与方法、函数和构造函数定义搭配使用,以指定函数参数的类型。@param 标记必须与函数定义中的参数顺序相同。

@param 标记后面必须跟类型表达式

或者,您也可以用内嵌方式为参数添加注解(请参阅示例中的函数 foo)。

例如:


/**
 * Queries a Baz for items.
 * @param {number} groupNum Subgroup id to query.
 * @param {string|number|null} term An itemName,
 *     or itemId, or null to search everything.
 */
goog.Baz.prototype.query = function(groupNum, term) {
  ...
};

function foo(/** number */ a, /** number */ b) {
  return a - b + 1;
}
对于具有销毁格式的参数,您可以在类型注解后面使用任何有效的 JS 标识符的名称。
/**
 * @param {{name: string, age: number}} person
 */
function logPerson({name, age}) {
  console.log(`${name} is ${age} years old`);
}
@private

将成员标记为私享。只有同一文件中的代码才能访问标记为 @private 的全局变量和函数。标记为 @private 的构造函数只能由同一文件中的代码以及其静态和实例成员实例化。

还可以随时随地访问标记为 @private 的构造函数的公共静态属性,并且 instanceof 运算符可以随时访问 @private 成员。

例如:

/**
 * Handlers that are listening to this logger.
 * @private {Array<Function>}
 */
this.handlers_ = [];

@protected

表示成员或属性受到保护。

标有 @protected 的属性可供以下人员访问:

  • 同一文件中的所有代码
  • 定义此属性的类的任何子类的静态方法和实例方法。

例如:

/**
 * Sets the component's root element to the given element.
 * Considered protected and final.
 * @param {Element} element Root element for the component.
 * @protected
 */
goog.ui.Component.prototype.setElementInternal = function(element) {
  // ...
};
@record

将函数标记为结构接口。结构接口类似于名义 @interface,但允许使用隐式实现。这意味着,任何包含结构接口原型上定义的方法和属性的类都会实现该结构接口(无论它是否使用 @implements 标记)。如果记录类型和对象字面量包含必要属性,则它们还会隐式实现结构接口。

例如:

/**
 * Anything with a draw() method.
 * @record
 */
function Drawable() {};
Drawable.prototype.draw = function() {};

/**
 * A polygon.
 * @param {!Drawable} x
 */
function render(x) { x.draw(); };

var o = { draw() { /* ... */ } };
render(o);
@return

指定方法和函数定义的返回类型。@return 标记后面必须跟类型表达式

或者,您也可以以内嵌方式为返回值类型添加注解(请参阅示例中的函数 foo)。

如果不在外部函数中的返回值没有,那么您可以省略 @return 标记,编译器会假定该函数返回 undefined

例如:

/**
 * Returns the ID of the last item.
 * @return {string} The hex ID.
 */
goog.Baz.prototype.getLastId = function() {
  ...
  return id;
};

function /** number */ foo(x) { return x - 1; }
@struct

@struct 用于创建具有固定数量属性的对象。使用 @struct 为构造函数(示例中的 Foo)添加注解时,您只能使用点表示法来访问 Foo 对象的属性,而不能使用括号表示法。此外,在构建 Foo 实例后,您无法向其中添加属性。该注解也可以直接用于对象字面量。

例如:

/**
 * @constructor
 * @struct
 */
function Foo(x) {
  this.x = x;
}
var obj1 = new Foo(123);
var someVar = obj1.x;  // OK
obj1.x = "qwerty";  // OK
obj1['x'] = "asdf";  // warning
obj1.y = 5;  // warning

var obj2 = /** @struct */ { x: 321 };
obj2['x'] = 123;  // warning
@template

请参阅通用类型

例如:

/**
 * @param {T} t
 * @constructor
 * @template T
 */
Container = function(t) { ... };
@this

指定关键字 this 在函数中引用的对象类型。@this 标记后面必须跟类型表达式

为防止编译器发出警告,每当 this 出现在函数(既不是原型方法也不是标记为 @constructor 的函数)中时,您必须使用 @this 注解。

例如:

chat.RosterWidget.extern('getRosterElement',
    /**
     * Returns the roster widget element.
     * @this {Widget}
     * @return {Element}
     */
    function() {
      return this.getComponent().getElement();
    });
@throws

用于记录函数抛出的异常。 类型检查工具目前没有使用此信息。它仅用于确定 extern 文件中声明的函数是否会产生副作用。

例如:

/**
 * @throws {DOMException}
 */
DOMApplicationCache.prototype.swapCache = function() { ... };
@type

标识变量、属性或表达式的类型。@type 标记后面必须跟类型表达式

声明变量或函数参数时,您可以编写内嵌代码(省略 {}@type),如第二个示例所示。 此快捷方式只能在声明变量或函数参数时完成。如果以后要调整类型,您需要类型转换

例如:

/**
 * The message hex ID.
 * @type {string}
 */
var hexId = hexId;
var /** string */ name = 'Jamie';
function useSomething(/** (string|number|!Object) */ something) {
...
}
@typedef

为更复杂的类型声明别名。 目前,类型定义只能在顶层定义,不能在函数内部定义。我们修复了新类型推断中的这一限制。

例如:

/** @typedef {(string|number)} */
goog.NumberLike;

/** @param {goog.NumberLike} x A number or a string. */
goog.readNumber = function(x) {
  ...
}
@unrestricted

表示某个类既不是 @struct 类型,也不是 @dict 类型。这是默认设置,因此通常无需明确编写,除非您使用 goog.defineClassclass 关键字,它们在默认情况下会生成类的 @struct

例如:

/**
 * @constructor
 * @unrestricted
 */
function Foo(x) {
  this.x = x;
}
var obj1 = new Foo(123);
var someVar = obj1.x;  // OK
obj1.x = "qwerty";  // OK
obj1['x'] = "asdf";  // OK
obj1.y = 5;  // OK

类型表达式

您可以使用类型表达式指定任何变量、属性、表达式或函数参数的数据类型。类型表达式由大括号(“{ }”)构成,大括号中包含下述类型运算符的部分组合。

使用类型表达式和 @param 标记来声明函数参数的类型,使用类型表达式和 @type 标记来声明变量、属性或表达式的类型。

您在代码中指定的类型越多,编译器可以执行的优化就越多,可发现的错误就越多。

编译器使用这些注解对程序进行类型检查。 请注意,Closure 编译器不保证一定能理解程序中每个表达式的类型。系统会尽最大努力查看变量的使用方式,以及附加到其声明的类型注解。然后,它会使用多种类型推断算法计算出尽可能多的表达式的类型。其中一些算法很简单(“如果 x 是一个数字,并且我们看到 y = x;,那么 y 是一个数字)。有些是更间接的代码(“如果 f 的第一个参数记录为必须接受数字的回调,并且我们看到 f(function(x) { /** ... */ });,那么 x 必须是数字)。

运算符名称 语法示例 说明
类型名称 {boolean}
{Window}
{goog.ui.Menu}
指定类型的名称。
类型:应用 {Array<string>}
字符串数组。

{Object<string, number>}
一个对象,其中的键为字符串,值为数字。

使用一组类型参数对类型进行参数化。类似于 Java 泛型。
类型联合 {(number|boolean)}
数字或布尔值。

请注意括号,这是必需的。
表示某个值可能具有 A 类型或 B 类型。
记录类型 {{myNum: number, myObject}}
具有名为 myNum 的属性(值为 number 的类型)和名为 myObject 的属性(具有任何类型的值)的匿名类型。

表示该值具有具有指定类型值的指定成员。

大括号是类型语法的一部分。例如,如需表示具有 length 属性的对象的 Array,您可以这样编写:
Array<{length}>。在左侧的示例中,外部大括号表示这是一个类型表达式,内部大括号表示这是一个记录类型。

可为 null 类型 {?number}
数字或 null

表示值为 A 或 null 类型。

默认情况下,无论是否使用 Nullable 运算符声明这些对象类型,它们均可为 null。对象类型是指除函数、字符串、数字或布尔值外的任何内容。如需将对象类型设为不可为 null,请使用不可为 null 运算符。

不可为 null 类型 {!Object}
对象,但绝不是 null 值。

表示值类型为 A 而不是 null。

默认情况下,无论函数和所有值类型(布尔值、数字和字符串)是否使用不可为 null 的运算符进行声明,它们都不可为 null。如需将值或函数类型设为可为 null,请使用 Nullable 运算符。

函数类型 {function(string, boolean)}
接受两个参数(一个字符串和一个布尔值)且返回值未知的函数。
指定函数及其函数类型。
函数返回值类型 {function(): number}
不接受任何参数并返回数字的函数。
指定函数的返回值的类型。
函数 this 类型 {function(this:goog.ui.Menu, string)}
该函数接受一个参数(字符串),并在 goog.ui.Menu 中执行。
指定函数中 this 的值的类型。
函数 new 类型 {function(new:goog.ui.Menu, string)}
该函数接受一个参数(字符串),并使用“new”关键字调用时创建新的 goog.ui.Menu 实例。
指定构造函数的构造类型。
变量参数 {function(string, ...number): number}
该函数接受一个参数(一个字符串),后跟一个可变数量(必须为数字)的参数。
表示函数类型接受可变数量的参数,并为变量参数指定类型。
变量参数(在 @param 注解中) @param {...number} var_args
带有注解的函数的可变数量的参数。
表示带注释的函数接受可变数量的参数,并为变量参数指定类型。
@param 注解中的可选参数 @param {number=} opt_argument
类型为 number 的可选参数。

表示由 @param 注解描述的参数是可选的。函数调用可以省略可选参数。可选参数不能放在参数列表中的非可选参数前面。

如果方法调用省略了可选参数,则该参数的值为 undefined。因此,如果该方法将参数的值存储在类属性中,则该属性的类型声明必须包含可能的 undefined 值,如以下示例所示:

/**
 * Some class, initialized with an optional value.
 * @param {Object=} opt_value Some value (optional).
 * @constructor
 */
function MyClass(opt_value) {
  /**
   * Some value.
   * @type {Object|undefined}
   */
  this.myValue = opt_value;
}
函数类型中的可选参数 {function(?string=, number=)}
将一个可选且可为 null 的字符串和一个可选数字作为参数的函数。
表示函数类型中的参数是可选的。函数调用中可以省略可选参数。可选参数不能放在参数列表中的非可选参数之前。
ALL 类型 {*} 表示该变量可采用任何类型。
UNKNOWN 类型 {?} 表示变量可采用任何类型,编译器不应对其类型进行任何类型检查。

类型转换

要将值的类型转换为特定类型,请使用以下语法

/** @type {!MyType} */ (valueExpression)
表达式的圆括号始终是必填项。

通用类型

与 Java 非常相似,Closure 编译器支持通用类型、函数和方法。泛型可处理各种类型的对象,同时保持编译时类型的安全性。

您可以使用泛型来实现泛型集合,以存储对特定类型的对象的引用,以及对特定类型的对象执行操作的泛化算法。

声明通用类型

通过将 @template 注解添加到类型的构造函数(对于类)或接口声明(对于接口),可以使类型变为通用类型。例如:

/**
 * @constructor
 * @template T
 */
Foo = function() { ... };

注释 @template T 表示 Foo 是一种通用类型,具有一个模板类型 T。模板类型 T 可用作 Foo 定义范围内的类型。例如:

/** @return {T} */
Foo.prototype.get = function() { ... };

/** @param {T} t */
Foo.prototype.set = function(t) { ... };

方法 get 将返回一个 T 类型的对象,并且 set 方法仅接受 T 类型的对象。

实例化通用类型

通过重复使用上面的示例,您可以通过多种方式创建 Foo 的模板实例:

/** @type {!Foo<string>} */ var foo = new Foo();
var foo = /** @type {!Foo<string>} */ (new Foo());

上述两种构造函数语句都会创建Foo实例,其模板类型 Tstring。编译器会遵循模板化类型,强制调用 foo 的方法以及访问 foo 的属性。例如:

foo.set("hello");  // OK.
foo.set(3);        // Error - expected a string, found a number.
var x = foo.get(); // x is a string.

实例的构造函数参数也可以隐式输入。考虑其他通用类型 Bar

/**
 * @param {T} t
 * @constructor
 * @template T
 */
Bar = function(t) { ... };
var bar = new Bar("hello"); // bar is a Bar<string>

Bar 构造函数的参数类型会推断为 string,因此创建的实例 bar 会推断为 Bar<string>

多个模板类型

通用模板可拥有任意数量的模板类型。以下映射类有两种模板类型:

/**
 * @constructor
 * @template Key, Val
 */
MyMap = function() { ... };

通用类型的所有模板类型必须在同一 @template 注解中指定为英文逗号分隔列表。模板类型名称的顺序很重要,因为模板化类型注释将使用排序规则将模板类型与值配对。例如:

/** @type {MyMap<string, number>} */ var map; // Key = string, Val = number.

泛型类型的不变性

Closure 编译器强制执行不变的通用类型。这意味着,如果上下文需要 Foo<X> 类型,则当 XY 是不同的类型时,您不能传递 Foo<Y> 类型,即使其中一个是另一个的子类型也是如此。例如:

/**
 * @constructor
 */
X = function() { ... };

/**
 * @extends {X}
 * @constructor
 */
Y = function() { ... };

/** @type {Foo<X>} */ var fooX;
/** @type {Foo<Y>} */ var fooY;

fooX = fooY; // Error
fooY = fooX; // Error

/** @param {Foo<Y>} fooY */
takesFooY = function(fooY) { ... };

takesFooY(fooY); // OK.
takesFooY(fooX); // Error

泛型类型的继承

通用类型可以继承,其模板类型既可以是固定的,也可以传播到继承类型。以下示例展示了一个继承类型,用于修复其父类型的模板类型:

/**
 * @constructor
 * @template T
 */
A = function() { ... };

/** @param {T} t */
A.prototype.method = function(t) { ... };

/**
 * @constructor
 * @extends {A<string>}
 */
B = function() { ... };

通过扩展 A<string>B 将具有 method 方法,该方法采用 string 类型的参数。

以下示例展示了继承其超类型的模板类型的继承类型:

/**
 * @constructor
 * @template U
 * @extends {A<U>}
 */
C = function() { ... };

通过扩展 A<U>C 的模板实例将具有 method 方法,该方法接受模板类型 U 的参数。

接口可以以类似的方式实现和扩展,但单个类型无法使用不同的模板类型多次实现同一接口。例如:

/**
 * @interface
 * @template T
 */
Foo = function() {};

/** @return {T} */
Foo.prototype.get = function() {};

/**
 * @constructor
 * @implements {Foo<string>}
 * @implements {Foo<number>}
 */
FooImpl = function() { ... }; // Error - implements the same interface twice

通用函数和方法

与通用类型类似,可以通过将 @template 注解添加到定义中来使函数和方法成为通用类型。例如:

/**
 * @param {T} a
 * @return {T}
 * @template T
 */
identity = function(a) { return a; };

/** @type {string} */ var msg = identity("hello") + identity("world"); // OK
/** @type {number} */ var sum = identity(2) + identity(2); // OK
/** @type {number} */ var sum = identity(2) + identity("2"); // Type mismatch