注释

使用 JSDoc

我们使用JSDoc中的注释风格. 行内注释使用 // 变量 的形式. 另外, 我们也遵循C++ 代码注释风格. 这也就是说你需要:

  • 版权和著作权的信息,
  • 文件注释中应该写明该文件的基本信息(如, 这段代码的功能摘要, 如何使用, 与哪些东西相关), 来告诉那些不熟悉代码的读者.
  • 类, 函数, 变量和必要的注释,
  • 期望在哪些浏览器中执行,
  • 正确的大小写, 标点和拼写.

为了避免出现句子片段, 请以合适的大/小写单词开头, 并以合适的标点符号结束这个句子.

现在假设维护这段代码的是一位初学者. 这可能正好是这样的!

目前很多编译器可从 JSDoc 中提取类型信息, 来对代码进行验证, 删除和压缩. 因此, 你很有必要去熟悉正确完整的 JSDoc .

顶层/文件注释

顶层注释用于告诉不熟悉这段代码的读者这个文件中包含哪些东西.

应该提供文件的大体内容, 它的作者, 依赖关系和兼容性信息. 如下:

// Copyright 2009 Google Inc. All Rights Reserved.

/**
 * @fileoverview Description of file, its uses and information
 * about its dependencies.
 * @author [email protected] (Firstname Lastname)
 */

类注释

每个类的定义都要附带一份注释, 描述类的功能和用法.也需要说明构造器参数.

如果该类继承自其它类, 应该使用 @extends 标记.

如果该类是对接口的实现, 应该使用 @implements 标记.

/**
 * Class making something fun and easy.
 * @param {string} arg1 An argument that makes this more interesting.
 * @param {Array.<number>} arg2 List of numbers to be processed.
 * @constructor
 * @extends {goog.Disposable}
 */
project.MyClass = function(arg1, arg2) {
  // ...
};
goog.inherits(project.MyClass, goog.Disposable);

方法与函数的注释

提供参数的说明. 使用完整的句子, 并用第三人称来书写方法说明.

/**
 * Converts text to some completely different text.
 * @param {string} arg1 An argument that makes this more interesting.
 * @return {string} Some return value.
 */
project.MyClass.prototype.someMethod = function(arg1) {
  // ...
};

/**
 * Operates on an instance of MyClass and returns something.
 * @param {project.MyClass} obj Instance of MyClass which leads to a long
 *     comment that needs to be wrapped to two lines.
 * @return {boolean} Whether something occured.
 */
function PR_someMethod(obj) {
  // ...
}

对于一些简单的, 不带参数的 getters, 说明可以忽略.

/**
 * @return {Element} The element for the component.
 */
goog.ui.Component.prototype.getElement = function() {
  return this.element_;
};

属性注释

也需要对属性进行注释.

/**
 * Maximum number of things per pane.
 * @type {number}
 */
project.MyClass.prototype.someProperty = 4;

类型转换的注释

有时, 类型检查不能很准确地推断出表达式的类型, 所以应该给它添加类型标记注释来明确之, 并且必须在表达式和类型标签外面包裹括号.

/** @type {number} */ (x)
(/** @type {number} */ x)

JSDoc 缩进

如果你在 @param, @return, @supported, @this@deprecated 中断行, 需要像在代码中一样, 使用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.
 */
project.MyClass.prototype.method = function(foo) {
  return 5;
};

不要在 @fileoverview 标记中进行缩进.

虽然不建议, 但也可对说明文字进行适当的排版对齐. 不过, 这样带来一些负面影响, 就是当你每次修改变量名时, 都得重新排版说明文字以保持和变量名对齐.

/**
 * This is NOT the preferred indentation method.
 * @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.
 */
project.MyClass.prototype.method = function(foo) {
  return 5;
};

枚举

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

注意一下, 枚举也具有有效类型, 所以可以当成参数类型来用.

/**
 * Sets project state.
 * @param {project.TriState} state New project state.
 */
project.setState = function(state) {
  // ...
};

Typedefs

有时类型会很复杂. 比如下面的函数, 接收 Element 参数:

/**
 * @param {string} tagName
 * @param {(string|Element|Text|Array.<Element>|Array.<Text>)} contents
 * @return {Element}
 */
goog.createElement = function(tagName, contents) {
  ...
};

你可以使用 @typedef 标记来定义个常用的类型表达式.


/**
* @param {string} tagName
* @param {goog.ElementContent} contents
* @return {Element}
*/
goog.createElement = function(tagName, contents) {
...
};

JSDoc 标记表

@param

模板 & 例子:

@param {Type} 变量名 描述

如:

/**
* 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) {
// ...
};

描述:给方法, 函数, 构造器中的参数添加说明.

类型检测支持:完全支持.

@return

模板 & 例子:

@return {Type} 描述

如:

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

描述:给方法, 函数的返回值添加说明. 在描述布尔型参数时,

用 “Whether the component is visible” 这种描述优于 “True if the component is visible, false otherwise”.

如果函数没有返回值, 就不需要添加@return标记.

类型检测支持:完全支持.

@author

模板 & 例子:

@author [email protected] (first last)

如:

/**
* @fileoverview Utilities for handling textareas.
* @author [email protected] (Uthur Pendragon)
*/

描述:表明文件的作者,通常仅会在@fileoverview注释中使用到它.

类型检测支持:不需要.

@see

模板 & 例子:

@see Link

如:

/**
* Adds a single item, recklessly.
* @see #addSafely
* @see goog.Collect
* @see goog.RecklessAdder#add
...

描述:给出引用链接, 用于进一步查看函数/方法的相关细节.

类型检测支持:不需要.

@fileoverview

模板 & 例子:

@fileoverview 描述

如:

/**
* @fileoverview Utilities for doing things that require this very long
* but not indented comment.
* @author [email protected] (Uthur Pendragon)
*/

描述:文件通览.

类型检测支持:不需要.

@constructor

模板 & 例子:

@constructor

如:

/**
* A rectangle.
* @constructor
*/
function GM_Rect() {
...
}

描述:指明类中的构造器.

类型检测支持:会检查. 如果省略了, 编译器将禁止实例化.

@interface

模板 & 例子:

@interface

如:

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

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

描述:指明这个函数是一个接口.

类型检测支持:会检查. 如果实例化一个接口, 编译器会警告.

@type

模板 & 例子:

@type Type
@type {Type}

如:

/**
* The message hex ID.
* @type {string}
*/
var hexId = hexId;

描述:标识变量, 属性或表达式的类型.

大多数类型是不需要加大括号的, 但为了保持一致, 建议统一加大括号. |

类型检测支持:会检查

@extends

模板 & 例子:

@extends Type
@extends {Type}

如:

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

描述:与 @constructor 一起使用, 用来表明该类是扩展自其它类的. 类型外的大括号可写可不写.

类型检测支持:会检查

@implements

模板 & 例子:

@implements Type
@implements {Type}

如:

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

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

描述:与 @constructor 一起使用, 用来表明该类实现自一个接口. 类型外的大括号可写可不写.

类型检测支持:会检查. 如果接口不完整, 编译器会警告.

@lends

模板 & 例子:

@lends objectName
@lends {objectName}

如:

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

描述:表示把对象的键看成是其他对象的属性. 该标记只能出现在对象语法中.

注意, 括号中的名称和其他标记中的类型名称不一样, 它是一个对象名, 以”借过来”的属性名命名.

如, @type {Foo} 表示 “Foo 的一个实例”, but @lends {Foo} 表示 “Foo 构造器”.

更多有关此标记的内容见JSDoc Toolkit docs.

类型检测支持:会检查

@private

模板 & 例子:

@private

如:

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

描述:指明那些以下划线结尾的方法和属性是

私有的.

不推荐使用后缀下划线, 而应改用 @private.

类型检测支持:需要指定标志来开启.

@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) {
// ...
};

描述:指明接下来的方法和属性是被保护的.

被保护的方法和属性的命名不需要以下划线结尾, 和普通变量名没区别. |

类型检测支持:需要指定标志来开启.

@this

模板 & 例子:

@this Type
@this {Type}

如:

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

描述:指明调用这个方法时, 需要在哪个上下文中. 当 this 指向的不是原型方法的函数时必须使用这个标记.

类型检测支持:会检查

@supported

模板 & 例子:

@supported 描述

如:

/**
* @fileoverview Event Manager
* Provides an abstracted interface to the
* browsers' event systems.
* @supported So far tested in IE6 and FF1.5
*/

描述:在文件概述中用到, 表明支持哪些浏览器.

类型检测支持:不需要.

@enum

模板 & 例子:

@enum {Type}

如:

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

描述:用于枚举类型.

类型检测支持:完全支持. 如果省略, 会认为是整型.

@deprecated

模板 & 例子:

@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) {
// ...
};

描述:告诉其他开发人员, 此方法, 函数已经过时, 不要再使用. 同时也会给出替代方法或函数.

类型检测支持:不需要

@override

模板 & 例子:

@override

如:

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

描述:指明子类的方法和属性是故意隐藏了父类的方法和属性. 如果子类的方法和属性没有自己的文档, 就会继承父类的.

类型检测支持:会检查

@inheritDoc

模板 & 例子:

@inheritDoc

如:

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

描述:指明子类的方法和属性是故意隐藏了父类的方法和属性, 它们具有相同的文档. 注意: 使用@inheritDoc 意味着也同时使用了 @override. |

类型检测支持:会检查

@code

模板 & 例子:

{@code …}

如:

/**
* Moves to the next position in the selection.
* Throws {@code goog.iter.StopIteration} when it
* passes the end of the range.
* @return {Node} The node at the next position.
*/
goog.dom.RangeIterator.prototype.next = function() {
// ...
};

描述:说明这是一段代码, 让它能在生成的文档中正确的格式化.

类型检测支持:不适用.

@license or @preserve

模板 & 例子:

@license

描述:

如:

/**
* @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:
*/

描述:所有被标记为 @license 或 @preserve 的, 会被编译器保留不做任何修改而直接输出到最终文挡中.

这个标记让一些重要的信息(如法律许可或版权信息)原样保留, 同样, 文本中的换行也会被保留. |

类型检测支持:不需要.

@noalias

模板 & 例子:

@noalias

如:

/** @noalias */
function Range() {}

描述:在外部文件中使用, 告诉编译器不要为这个变量或函数重命名.

类型检测支持:不需要.

@define

模板 & 例子:

@define {Type} 描述

如:

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

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

描述:表示该变量可在编译时被编译器重新赋值.

在上面例子中, BUILD 文件中指定了

–define=’goog.userAgent.ASSUME_IE=true’

这个编译之后, 常量 goog.userAgent.ASSUME_IE 将被全部直接替换为 true.

类型检测支持:不需要.

@export

模板 & 例子:

@export

如:

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

描述:

上面的例子代码, 当编译器运行时指定 –generate_exports 标志, 会生成下面的代码:

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

编译后, 将源代码中的名字原样导出.

使用 @export 标记时, 应该

  1. 包含 //javascript/closure/base.js, 或者
  2. 在代码库中自定义 goog.exportSymbol 和 goog.exportProperty 两个方法, 并保证有相同的调用方式.

类型检测支持:不需要.

@const

模板 & 例子:

@const

如:

/** @const */ var MY_BEER = 'stout';

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

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

描述:

声明变量为只读, 直接写在一行上.

如果其他代码中重写该变量值, 编译器会警告.

常量应全部用大写字符, 不过使用这个标记, 可以帮你消除命名上依赖.

虽然 jsdoc.org 上列出的 @final 标记作用等价于 @const , 但不建议使用.

@const 与 JS1.5 中的 const 关键字一致.

注意, 编译器不禁止修改常量对象的属性(这与 C++ 中的常量定义不一样).

如果可以准确推测出常量类型的话,那么类型申明可以忽略. 如果指定了类型, 应该也写在同一行上.

变量的额外注释可写可不写.

类型检测支持:支持.

@nosideeffects

模板 & 例子:

@nosideeffects

如:

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

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

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

描述:用于对函数或构造器声明, 说明调用此函数不会有副作用. 编译器遇到此标记时, 如果调用函数的返回值没有其他地方使用到, 则会将这个函数整个删除.

类型检测支持:不需要检查.

@typedef

模板 & 例子:

@typedef

如:

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

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

描述:这个标记用于给一个复杂的类型取一个别名.

类型检测支持:会检查

@externs

模板 & 例子:

@externs

如:

/**
* @fileoverview This is an externs file.
* @externs
*/

var document;

描述:

指明一个外部文件.

类型检测支持:不会检查

在第三方代码中, 你还会见到其他一些 JSDoc 标记. 这些标记在JSDoc Toolkit Tag Reference都有介绍到, 但在 Google 的代码中, 目前不推荐使用. 你可以认为这些是将来会用到的 “保留” 名. 它们包含:

  • @augments
  • @argument
  • @borrows
  • @class
  • @constant
  • @constructs
  • @default
  • @event
  • @example
  • @field
  • @function
  • @ignore
  • @inner
  • @link
  • @memberOf
  • @name
  • @namespace
  • @property
  • @public
  • @requires
  • @returns
  • @since
  • @static
  • @version

JSDoc 中的 HTML

类似于 JavaDoc, JSDoc 支持许多 HTML 标签, 如 <code>, <pre>, <strong>, <ul>, <ol>, <li>, <a>, 等等.

这就是说 JSDoc 不会完全依照纯文本中书写的格式. 所以, 不要在 JSDoc 中, 使用空白字符来做格式化:

/**
 * Computes weight based on three factors:
 *   items sent
 *   items received
 *   last timestamp
 */

上面的注释, 出来的结果是:

Computes weight based on three factors: items sent items received items received

应该这样写:

/**
 * Computes weight based on three factors:
 * <ul>
 * <li>items sent
 * <li>items received
 * <li>last timestamp
 * </ul>
 */

另外, 也不要包含任何 HTML 或类 HTML 标签, 除非你就想让它们解析成 HTML 标签.

/**
 * Changes <b> tags to  tags.
 */

出来的结果是:

Changes tags to tags.

另外, 也应该在源代码文件中让其他人更可读, 所以不要过于使用 HTML 标签:

/**
 * Changes &lt;b&gt; tags to &lt;span&gt; tags.
 */

上面的代码中, 其他人就很难知道你想干嘛, 直接改成下面的样子就清楚多了:

/**
* Changes 'b' tags to 'span' tags.
*/

编译

推荐使用

建议您去使用 JS 编译器, 如 Closure Compiler.

Tips and Tricks

JavaScript 小技巧

True 和 False 布尔表达式

下面的布尔表达式都返回 false:

  • null
  • undefined
  • '' 空字符串
  • 0 数字0

但小心下面的, 可都返回 true:

  • '0' 字符串0
  • [] 空数组
  • {} 空对象

下面段比较糟糕的代码:

while (x != null) {

你可以直接写成下面的形式(只要你希望 x 不是 0 和空字符串, 和 false):

如果你想检查字符串是否为 null 或空:

if (y != null && y != '') {

但这样会更好:

注意: 还有很多需要注意的地方, 如:

  • Boolean('0') == true '0' != true

  • 0 != null 0 == [] 0 == false

  • Boolean(null) == false null != true null != false

  • Boolean(undefined) == false undefined != true undefined != false

  • Boolean([]) == true [] != true [] == false

  • Boolean({}) == true {} != true {} != false

条件(三元)操作符 (?:)

三元操作符用于替代下面的代码:

if (val != 0) {
  return foo();
} else {
  return bar();
}

你可以写成:

在生成 HTML 代码时也是很有用的:

var html = '<input type="checkbox"' +
    (isChecked ? ' checked' : '') +
    (isEnabled ? '' : ' disabled') +
    ' name="foo">';

&&||

二元布尔操作符是可短路的, 只有在必要时才会计算到最后一项.

“||” 被称作为 ‘default’ 操作符, 因为可以这样:

/** @param {*=} opt_win */
function foo(opt_win) {
  var win;
  if (opt_win) {
    win = opt_win;
  } else {
    win = window;
  }
  // ...
}

你可以使用它来简化上面的代码:

/** @param {*=} opt_win */
function foo(opt_win) {
  var win = opt_win || window;
  // ...
}

“&&” 也可简短代码.比如:

if (node) {
  if (node.kids) {
    if (node.kids[index]) {
      foo(node.kids[index]);
    }
  }
}

你可以像这样来使用:

if (node && node.kids && node.kids[index]) {
  foo(node.kids[index]);
}

或者:

var kid = node && node.kids && node.kids[index];
if (kid) {
  foo(kid);
}

不过这样就有点儿过头了:

node && node.kids && node.kids[index] && foo(node.kids[index]);

使用 join() 来创建字符串

通常是这样使用的:

function listHtml(items) {
  var html = '';
  for (var i = 0; i < items.length; ++i) {
    if (i > 0) {
      html += ', ';
    }
    html += itemHtml(items[i]);
  }
  html += '';
  return html;
}

但这样在 IE 下非常慢, 可以用下面的方式:

function listHtml(items) {
  var html = [];
  for (var i = 0; i < items.length; ++i) {
    html[i] = itemHtml(items[i]);
  }
  return '' + html.join(', ') + '';
}

你也可以是用数组作为字符串构造器, 然后通过 myArray.join('') 转换成字符串. 不过由于赋值操作快于数组的 push(), 所以尽量使用赋值操作.

遍历 Node List

Node lists 是通过给节点迭代器加一个过滤器来实现的.

这表示获取他的属性, 如 length 的时间复杂度为 O(n), 通过 length 来遍历整个列表需要 O(n^2).

var paragraphs = document.getElementsByTagName('p');
for (var i = 0; i < paragraphs.length; i++) {
  doSomething(paragraphs[i]);
}

这样做会更好:

var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
  doSomething(paragraph);
}

这种方法对所有的 collections 和数组(只要数组不包含 falsy 值) 都适用.

在上面的例子中, 也可以通过 firstChild 和 nextSibling 来遍历孩子节点.

var parentNode = document.getElementById('foo');
for (var child = parentNode.firstChild; child; child = child.nextSibling) {
  doSomething(child);
}