## 注釋
使用 JSDoc
我們使用[JSDoc](http://code.google.com/p/jsdoc-toolkit/)中的注釋風格. 行內注釋使用 // 變量 的形式. 另外, 我們也遵循[C++ 代碼注釋風格](http://docs.kissyui.com/docs/html/styleguide/google/cppguide.xml#Comments). 這也就是說你需要:
* 版權和著作權的信息,
* 文件注釋中應該寫明該文件的基本信息(如, 這段代碼的功能摘要, 如何使用, 與哪些東西相關), 來告訴那些不熟悉代碼的讀者.
* 類, 函數, 變量和必要的注釋,
* 期望在哪些瀏覽器中執行,
* 正確的大小寫, 標點和拼寫.
為了避免出現句子片段, 請以合適的大/小寫單詞開頭, 并以合適的標點符號結束這個句子.
現在假設維護這段代碼的是一位初學者. 這可能正好是這樣的!
目前很多編譯器可從 JSDoc 中提取類型信息, 來對代碼進行驗證, 刪除和壓縮. 因此, 你很有必要去熟悉正確完整的 JSDoc .
### 頂層/文件注釋
頂層注釋用于告訴不熟悉這段代碼的讀者這個文件中包含哪些東西.
應該提供文件的大體內容, 它的作者, 依賴關系和兼容性信息. 如下:
```
// Copyright 2009 Google Inc. All Rights Reserved.
/**
* @fileoverview Description of file, its uses and information
* about its dependencies.
* @author user@google.com (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
};
```
注意一下, 枚舉也具有有效[類型](#JavaScript_Types), 所以可以當成參數類型來用.
```
/**
* 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 username@google.com (first last)
```
_如:_
```
/**
* @fileoverview Utilities for handling textareas.
* @author kuth@google.com (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 kuth@google.com (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](http://code.google.com/p/jsdoc-toolkit/wiki/TagLends).
類型檢測支持:會檢查
#### `@private`
模板 & 例子:
```
@private
```
_如:_
```
/**
* Handlers that are listening to this logger.
* @type Array.<Function>
* @private
*/
this.handlers_ = [];
```
描述:指明那些以下劃線結尾的方法和屬性是
[私有](#Visibility__private_and_protected_fields_)的.
不推薦使用后綴下劃線, 而應改用 @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) {
// ...
};
```
描述:指明接下來的方法和屬性是[被保護](#Visibility__private_and_protected_fields_)的.
被保護的方法和屬性的命名不需要以下劃線結尾, 和普通變量名沒區別. |
類型檢測支持:需要指定標志來開啟.
#### `@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](http://code.google.com/p/jsdoc-toolkit/wiki/TagReference)都有介紹到, 但在 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 <b> tags to <span> tags.
*/
```
上面的代碼中, 其他人就很難知道你想干嘛, 直接改成下面的樣子就清楚多了:
```
/**
* Changes 'b' tags to 'span' tags.
*/
```
### 編譯
推薦使用
建議您去使用 JS 編譯器, 如 [Closure Compiler](http://code.google.com/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);
}
```