Google JavaScript 編碼規(guī)范指南
修訂版: 2.9
Aaron WhyteBob Jervis
Dan Pupius
Eric Arvidsson
Fritz Schneider
Robby Walker
每個(gè)條目都有概述信息, 點(diǎn)擊 ▽ 查看詳細(xì)的內(nèi)容. 你也可以點(diǎn)擊下面的按鈕
重要注意事項(xiàng)
顯示被隱藏的內(nèi)容
Hooray! 這里是更多詳細(xì)的內(nèi)容, 你也可以點(diǎn)擊最上面的"顯示/隱藏全部按鈕"來切換顯示更多內(nèi)容.
背景
JavaScript 是一種客戶端腳本語言, Google 的許多開源工程中都有用到它. 這份指南列出了編寫 JavaScript 時(shí)需要遵守的規(guī)則.
JavaScript 語言規(guī)范
變量
var
關(guān)鍵字.Decision:當(dāng)你沒有寫 var
, 變量就會(huì)暴露在全局上下文中, 這樣很可能會(huì)和現(xiàn)有變量沖突. 另外, 如果沒有加上, 很難明確該變量的作用域是什么, 變量也很可能像在局部作用域中, 很輕易地泄漏到 Document 或者 Window 中, 所以務(wù)必用 var
去聲明變量.
常量
NAMES_LIKE_THIS
, 即使用大寫字符, 并用下劃線分隔. 你也可用 @const
標(biāo)記來指明它是一個(gè)常量. 但請(qǐng)永遠(yuǎn)不要使用 const
關(guān)鍵詞.Decision:
對(duì)于基本類型的常量, 只需轉(zhuǎn)換命名.
/** * The number of seconds in a minute. * @type {number} */ goog.example.SECONDS_IN_A_MINUTE = 60;
對(duì)于非基本類型, 使用 @const
標(biāo)記.
/** * The number of seconds in each of the given units. * @type {Object.<number>} * @const */ goog.example.SECONDS_TABLE = { minute: 60, hour: 60 * 60, day: 60 * 60 * 24 }
這標(biāo)記告訴編譯器它是常量.
至于關(guān)鍵詞 const
, 因?yàn)?IE 不能識(shí)別, 所以不要使用.
分號(hào)
如果僅依靠語句間的隱式分隔, 有時(shí)會(huì)很麻煩. 你自己更能清楚哪里是語句的起止.
而且有些情況下, 漏掉分號(hào)會(huì)很危險(xiǎn):
// 1. MyClass.prototype.myMethod = function() { return 42; } // No semicolon here. (function() { // Some initialization code wrapped in a function to create a scope for locals. })(); var x = { 'i': 1, 'j': 2 } // No semicolon here. // 2. Trying to do one thing on Internet Explorer and another on Firefox. // I know you'd never write code like this, but throw me a bone. [normalVersion, ffVersion][isIE](); var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here. // 3. conditional execution a la bash -1 == resultOfOperation() || die();
這段代碼會(huì)發(fā)生些什么詭異事呢?
- 報(bào) JavaScript 錯(cuò)誤 - 例子1上的語句會(huì)解釋成, 一個(gè)函數(shù)帶一匿名函數(shù)作為參數(shù)而被調(diào)用, 返回42后, 又一次被"調(diào)用", 這就導(dǎo)致了錯(cuò)誤.
- 例子2中, 你很可能會(huì)在運(yùn)行時(shí)遇到 'no such property in undefined' 錯(cuò)誤, 原因是代碼試圖這樣
x[ffVersion][isIE]()
執(zhí)行. - 當(dāng)
resultOfOperation()
返回非 NaN 時(shí), 就會(huì)調(diào)用die
, 其結(jié)果也會(huì)賦給THINGS_TO_EAT
.
為什么?
JavaScript 的語句以分號(hào)作為結(jié)束符, 除非可以非常準(zhǔn)確推斷某結(jié)束位置才會(huì)省略分號(hào). 上面的幾個(gè)例子產(chǎn)出錯(cuò)誤, 均是在語句中聲明了函數(shù)/對(duì)象/數(shù)組直接量, 但 閉括號(hào)('}'或']')并不足以表示該語句的結(jié)束. 在 JavaScript 中, 只有當(dāng)語句后的下一個(gè)符號(hào)是后綴或括號(hào)運(yùn)算符時(shí), 才會(huì)認(rèn)為該語句的結(jié)束.
遺漏分號(hào)有時(shí)會(huì)出現(xiàn)很奇怪的結(jié)果, 所以確保語句以分號(hào)結(jié)束.
塊內(nèi)函數(shù)聲明
不要寫成:
if (x) { function foo() {} }
雖然很多 JS 引擎都支持塊內(nèi)聲明函數(shù), 但它不屬于 ECMAScript 規(guī)范 (見 ECMA-262, 第13和14條). 各個(gè)瀏覽器糟糕的實(shí)現(xiàn)相互不兼容, 有些也與未來 ECMAScript 草案相違背. ECMAScript 只允許在腳本的根語句或函數(shù)中聲明函數(shù). 如果確實(shí)需要在塊中定義函數(shù), 建議使用函數(shù)表達(dá)式來初始化變量:
if (x) { var foo = function() {} }
自定義異常
有時(shí)發(fā)生異常了, 但返回的錯(cuò)誤信息比較奇怪, 也不易讀. 雖然可以將含錯(cuò)誤信息的引用對(duì)象或者可能產(chǎn)生錯(cuò)誤的完整對(duì)象傳遞過來, 但這樣做都不是很好, 最好還是自定義異常類, 其實(shí)這些基本上都是最原始的異常處理技巧. 所以在適當(dāng)?shù)臅r(shí)候使用自定義異常.
標(biāo)準(zhǔn)特性
最大化可移植性和兼容性, 盡量使用標(biāo)準(zhǔn)方法而不是用非標(biāo)準(zhǔn)方法, (比如, 優(yōu)先用string.charAt(3)
而不用 string[3]
, 通過 DOM 原生函數(shù)訪問元素, 而不是使用應(yīng)用封裝好的快速接口.
封裝基本類型
沒有任何理由去封裝基本類型, 另外還存在一些風(fēng)險(xiǎn):
var x = new Boolean(false); if (x) { alert('hi'); // Shows 'hi'. }
除非明確用于類型轉(zhuǎn)換, 其他情況請(qǐng)千萬不要這樣做!
var x = Boolean(0); if (x) { alert('hi'); // This will never be alerted. } typeof Boolean(0) == 'boolean'; typeof new Boolean(0) == 'object';
有時(shí)用作 number
, string
或 boolean
時(shí), 類型的轉(zhuǎn)換會(huì)非常實(shí)用.
多級(jí)原型結(jié)構(gòu)
多級(jí)原型結(jié)構(gòu)是指 JavaScript 中的繼承關(guān)系. 當(dāng)你自定義一個(gè)D類, 且把B類作為其原型, 那么這就獲得了一個(gè)多級(jí)原型結(jié)構(gòu). 這些原型結(jié)構(gòu)會(huì)變得越來越復(fù)雜!
使用 the Closure 庫 中的 goog.inherits()
或其他類似的用于繼承的函數(shù), 會(huì)是更好的選擇.
function D() { goog.base(this) } goog.inherits(D, B); D.prototype.method = function() { ... };
方法定義
Foo.prototype.bar = function() { ... };
有很多方法可以給構(gòu)造器添加方法或成員, 我們更傾向于使用如下的形式:
Foo.prototype.bar = function() { /* ... */ };
閉包
閉包也許是 JS 中最有用的特性了. 有一份比較好的介紹閉包原理的文檔.
有一點(diǎn)需要牢記, 閉包保留了一個(gè)指向它封閉作用域的指針, 所以, 在給 DOM 元素附加閉包時(shí), 很可能會(huì)產(chǎn)生循環(huán)引用, 進(jìn)一步導(dǎo)致內(nèi)存泄漏. 比如下面的代碼:
function foo(element, a, b) { element.onclick = function() { /* uses a and b */ }; }
這里, 即使沒有使用 element
, 閉包也保留了 element
, a
和 b
的引用, . 由于 element
也保留了對(duì)閉包的引用, 這就產(chǎn)生了循環(huán)引用, 這就不能被 GC 回收. 這種情況下, 可將代碼重構(gòu)為:
function foo(element, a, b) { element.onclick = bar(a, b); } function bar(a, b) { return function() { /* uses a and b */ } }
eval()
eval()
會(huì)讓程序執(zhí)行的比較混亂, 當(dāng) eval()
里面包含用戶輸入的話就更加危險(xiǎn). 可以用其他更佳的, 更清晰, 更安全的方式寫你的代碼, 所以一般情況下請(qǐng)不要使用 eval(). 當(dāng)碰到一些需要解析序列化串的情況下(如, 計(jì)算 RPC 響應(yīng)), 使用 eval
很容易實(shí)現(xiàn).
解析序列化串是指將字節(jié)流轉(zhuǎn)換成內(nèi)存中的數(shù)據(jù)結(jié)構(gòu). 比如, 你可能會(huì)將一個(gè)對(duì)象輸出成文件形式:
users = [ { name: 'Eric', id: 37824, email: 'jellyvore@myway.com' }, { name: 'xtof', id: 31337, email: 'b4d455h4x0r@google.com' }, ... ];
很簡單地調(diào)用 eval
后, 把表示成文件的數(shù)據(jù)讀取回內(nèi)存中.
類似的, eval()
對(duì) RPC 響應(yīng)值進(jìn)行解碼. 例如, 你在使用 XMLHttpRequest
發(fā)出一個(gè) RPC 請(qǐng)求后, 通過 eval () 將服務(wù)端的響應(yīng)文本轉(zhuǎn)成 JavaScript 對(duì)象:
var userOnline = false; var user = 'nusrat'; var xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', 'http://chat.google.com/isUserOnline?user=' + user, false); xmlhttp.send(''); // Server returns: // userOnline = true; if (xmlhttp.status == 200) { eval(xmlhttp.responseText); } // userOnline is now true.
with() {}
使用 with
讓你的代碼在語義上變得不清晰. 因?yàn)?nbsp;with
的對(duì)象, 可能會(huì)與局部變量產(chǎn)生沖突, 從而改變你程序原本的用義. 下面的代碼是干嘛的?
with (foo) { var x = 3; return x; }
答案: 任何事. 局部變量 x
可能被 foo
的屬性覆蓋, 當(dāng)它定義一個(gè) setter 時(shí), 在賦值 3
后會(huì)執(zhí)行很多其他代碼. 所以不要使用 with
語句.
this
this
的語義很特別. 有時(shí)它引用一個(gè)全局對(duì)象(大多數(shù)情況下), 調(diào)用者的作用域(使用 eval
時(shí)), DOM 樹中的節(jié)點(diǎn)(添加事件處理函數(shù)時(shí)), 新創(chuàng)建的對(duì)象(使用一個(gè)構(gòu)造器), 或者其他對(duì)象(如果函數(shù)被 call()
或 apply()
).
使用時(shí)很容易出錯(cuò), 所以只有在下面兩個(gè)情況時(shí)才能使用:
- 在構(gòu)造器中
- 對(duì)象的方法(包括創(chuàng)建的閉包)中
for-in 循環(huán)
對(duì) Array
用 for-in
循環(huán)有時(shí)會(huì)出錯(cuò). 因?yàn)樗⒉皇菑?nbsp;0
到 length - 1
進(jìn)行遍歷, 而是所有出現(xiàn)在對(duì)象及其原型鏈的鍵值. 下面就是一些失敗的使用案例:
function printArray(arr) { for (var key in arr) { print(arr[key]); } } printArray([0,1,2,3]); // This works. var a = new Array(10); printArray(a); // This is wrong. a = document.getElementsByTagName('*'); printArray(a); // This is wrong. a = [0,1,2,3]; a.buhu = 'wine'; printArray(a); // This is wrong again. a = new Array; a[3] = 3; printArray(a); // This is wrong again.
而遍歷數(shù)組通常用最普通的 for 循環(huán).
function printArray(arr) { var l = arr.length; for (var i = 0; i < l; i++) { print(arr[i]); } }
關(guān)聯(lián)數(shù)組
Array
作為 map/hash/associative 數(shù)組.數(shù)組中不允許使用非整型作為索引值, 所以也就不允許用關(guān)聯(lián)數(shù)組. 而取代它使用 Object
來表示 map/hash 對(duì)象. Array
僅僅是擴(kuò)展自 Object
(類似于其他 JS 中的對(duì)象, 就像 Date
, RegExp
和 String
)一樣來使用.
多行字符串
不要這樣寫長字符串:
var myString = 'A rather long string of English text, an error message \ actually that just keeps going and going -- an error \ message to make the Energizer bunny blush (right through \ those Schwarzenegger shades)! Where was I? Oh yes, \ you\'ve got an error and all the extraneous whitespace is \ just gravy. Have a nice day.';
在編譯時(shí), 不能忽略行起始位置的空白字符; "\" 后的空白字符會(huì)產(chǎn)生奇怪的錯(cuò)誤; 雖然大多數(shù)腳本引擎支持這種寫法, 但它不是 ECMAScript 的標(biāo)準(zhǔn)規(guī)范.
Array 和 Object 直接量
使用 Array
和 Object
語法, 而不使用 Array
和 Object
構(gòu)造器.
使用 Array 構(gòu)造器很容易因?yàn)閭鲄⒉磺‘?dāng)導(dǎo)致錯(cuò)誤.
// Length is 3. var a1 = new Array(x1, x2, x3); // Length is 2. var a2 = new Array(x1, x2); // If x1 is a number and it is a natural number the length will be x1. // If x1 is a number but not a natural number this will throw an exception. // Otherwise the array will have one element with x1 as its value. var a3 = new Array(x1); // Length is 0. var a4 = new Array();
如果傳入一個(gè)參數(shù)而不是2個(gè)參數(shù), 數(shù)組的長度很有可能就不是你期望的數(shù)值了.
為了避免這些歧義, 我們應(yīng)該使用更易讀的直接量來聲明.
var a = [x1, x2, x3]; var a2 = [x1, x2]; var a3 = [x1]; var a4 = [];
雖然 Object 構(gòu)造器沒有上述類似的問題, 但鑒于可讀性和一致性考慮, 最好還是在字面上更清晰地指明.
var o = new Object(); var o2 = new Object(); o2.a = 0; o2.b = 1; o2.c = 2; o2['strange key'] = 3;
應(yīng)該寫成:
var o = {}; var o2 = { a: 0, b: 1, c: 2, 'strange key': 3 };
修改內(nèi)置對(duì)象的原型
千萬不要修改內(nèi)置對(duì)象, 如 Object.prototype
和 Array.prototype
的原型. 而修改內(nèi)置對(duì)象, 如 Function.prototype
的原型, 雖然少危險(xiǎn)些, 但仍會(huì)導(dǎo)致調(diào)試時(shí)的詭異現(xiàn)象. 所以也要避免修改其原型.
IE下的條件注釋
不要這樣子寫:
var f = function () { /*@cc_on if (@_jscript) { return 2* @*/ 3; /*@ } @*/ };
條件注釋妨礙自動(dòng)化工具的執(zhí)行, 因?yàn)樵谶\(yùn)行時(shí), 它們會(huì)改變 JavaScript 語法樹.
JavaScript 編碼風(fēng)格
命名
通常, 使用 functionNamesLikeThis
, variableNamesLikeThis
, ClassNamesLikeThis
, EnumNamesLikeThis
, methodNamesLikeThis
, 和SYMBOLIC_CONSTANTS_LIKE_THIS
.
展開見細(xì)節(jié).
屬性和方法
- 文件或類中的 私有 屬性, 變量和方法名應(yīng)該以下劃線 "_" 開頭.
- 保護(hù) 屬性, 變量和方法名不需要下劃線開頭, 和公共變量名一樣.
更多有關(guān) 私有 和 保護(hù)的信息見, visibility.
方法和函數(shù)參數(shù)
可選參數(shù)以 opt_
開頭.
函數(shù)的參數(shù)個(gè)數(shù)不固定時(shí), 應(yīng)該添加最后一個(gè)參數(shù) var_args
為參數(shù)的個(gè)數(shù). 你也可以不設(shè)置 var_args
而取代使用 arguments
.
可選和可變參數(shù)應(yīng)該在 @param
標(biāo)記中說明清楚. 雖然這兩個(gè)規(guī)定對(duì)編譯器沒有任何影響, 但還是請(qǐng)盡量遵守
Getters 和 Setters
Getters 和 setters 并不是必要的. 但只要使用它們了, 就請(qǐng)將 getters 命名成 getFoo()
形式, 將 setters 命名成 setFoo(value)
形式. (對(duì)于布爾類型的 getters, 使用 isFoo()
也可.)
命名空間
JavaScript 不支持包和命名空間.
不容易發(fā)現(xiàn)和調(diào)試全局命名的沖突, 多個(gè)系統(tǒng)集成時(shí)還可能因?yàn)槊麤_突導(dǎo)致很嚴(yán)重的問題. 為了提高 JavaScript 代碼復(fù)用率, 我們遵循下面的約定以避免沖突.
為全局代碼使用命名空間
在全局作用域上, 使用一個(gè)唯一的, 與工程/庫相關(guān)的名字作為前綴標(biāo)識(shí). 比如, 你的工程是 "Project Sloth", 那么命名空間前綴可取為 sloth.*
.
var sloth = {}; sloth.sleep = function() { ... };
許多 JavaScript 庫, 包括 the Closure Library and Dojo toolkit 為你提供了聲明你自己的命名空間的函數(shù). 比如:
goog.provide('sloth'); sloth.sleep = function() { ... };
明確命名空間所有權(quán)
當(dāng)選擇了一個(gè)子命名空間, 請(qǐng)確保父命名空間的負(fù)責(zé)人知道你在用哪個(gè)子命名空間, 比如說, 你為工程 'sloths' 創(chuàng)建一個(gè) 'hats' 子命名空間, 那確保 Sloth 團(tuán)隊(duì)人員知道你在使用 sloth.hats
.
外部代碼和內(nèi)部代碼使用不同的命名空間
"外部代碼" 是指來自于你代碼體系的外部, 可以獨(dú)立編譯. 內(nèi)外部命名應(yīng)該嚴(yán)格保持獨(dú)立. 如果你使用了外部庫, 他的所有對(duì)象都在 foo.hats.*
下, 那么你自己的代碼不能在 foo.hats.*
下命名, 因?yàn)楹苡锌赡芷渌麍F(tuán)隊(duì)也在其中命名.
foo.require('foo.hats'); /** * WRONG -- Do NOT do this. * @constructor * @extend {foo.hats.RoundHat} */ foo.hats.BowlerHat = function() { };
如果你需要在外部命名空間中定義新的 API, 那么你應(yīng)該直接導(dǎo)出一份外部庫, 然后在這份代碼中修改. 在你的內(nèi)部代碼中, 應(yīng)該通過他們的內(nèi)部名字來調(diào)用內(nèi)部 API , 這樣保持一致性可讓編譯器更好的優(yōu)化你的代碼.
foo.provide('googleyhats.BowlerHat'); foo.require('foo.hats'); /** * @constructor * @extend {foo.hats.RoundHat} */ googleyhats.BowlerHat = function() { ... }; goog.exportSymbol('foo.hats.BowlerHat', googleyhats.BowlerHat);
重命名那些名字很長的變量, 提高可讀性
主要是為了提高可讀性. 局部空間中的變量別名只需要取原名字的最后部分.
/** * @constructor */ some.long.namespace.MyClass = function() { }; /** * @param {some.long.namespace.MyClass} a */ some.long.namespace.MyClass.staticHelper = function(a) { ... }; myapp.main = function() { var MyClass = some.long.namespace.MyClass; var staticHelper = some.long.namespace.MyClass.staticHelper; staticHelper(new MyClass()); };
不要對(duì)命名空間創(chuàng)建別名.
myapp.main = function() { var namespace = some.long.namespace; namespace.MyClass.staticHelper(new namespace.MyClass()); };
除非是枚舉類型, 不然不要訪問別名變量的屬性.
/** @enum {string} */ some.long.namespace.Fruit = { APPLE: 'a', BANANA: 'b' }; myapp.main = function() { var Fruit = some.long.namespace.Fruit; switch (fruit) { case Fruit.APPLE: ... case Fruit.BANANA: ... } };
myapp.main = function() { var MyClass = some.long.namespace.MyClass; MyClass.staticHelper(null); };
不要在全局范圍內(nèi)創(chuàng)建別名, 而僅在函數(shù)塊作用域中使用.
文件名
文件名應(yīng)該使用小寫字符, 以避免在有些系統(tǒng)平臺(tái)上不識(shí)別大小寫的命名方式. 文件名以.js
結(jié)尾, 不要包含除 -
和 _
外的標(biāo)點(diǎn)符號(hào)(使用 -
優(yōu)于 _
).
自定義 toString() 方法
可自定義 toString()
方法, 但確保你的實(shí)現(xiàn)方法滿足: (1) 總是成功 (2) 沒有其他負(fù)面影響. 如果不滿足這兩個(gè)條件, 那么可能會(huì)導(dǎo)致嚴(yán)重的問題, 比如, 如果 toString()
調(diào)用了包含 assert
的函數(shù), assert
輸出導(dǎo)致失敗的對(duì)象, 這在 toString()
也會(huì)被調(diào)用.
明確作用域
任何時(shí)候都要明確作用域 - 提高可移植性和清晰度. 例如, 不要依賴于作用域鏈中的 window
對(duì)象. 可能在其他應(yīng)用中, 你函數(shù)中的 window
不是指之前的那個(gè)窗口對(duì)象.
代碼格式化
主要依照C++ 格式規(guī)范 ( 中文版 ), 針對(duì) JavaScript, 還有下面一些附加說明.
大括號(hào)
分號(hào)會(huì)被隱式插入到代碼中, 所以你務(wù)必在同一行上插入大括號(hào). 例如:
if (something) { // ... } else { // ... }
數(shù)組和對(duì)象的初始化
如果初始值不是很長, 就保持寫在單行上:
var arr = [1, 2, 3]; // No space after [ or before ]. var obj = {a: 1, b: 2, c: 3}; // No space after { or before }.
初始值占用多行時(shí), 縮進(jìn)2個(gè)空格.
// Object initializer. var inset = { top: 10, right: 20, bottom: 15, left: 12 }; // Array initializer. this.rows_ = [ '"Slartibartfast" <fjordmaster@magrathea.com>', '"Zaphod Beeblebrox" <theprez@universe.gov>', '"Ford Prefect" <ford@theguide.com>', '"Arthur Dent" <has.no.tea@gmail.com>', '"Marvin the Paranoid Android" <marv@googlemail.com>', 'the.mice@magrathea.com' ]; // Used in a method call. goog.dom.createDom(goog.dom.TagName.DIV, { id: 'foo', className: 'some-css-class', style: 'display:none' }, 'Hello, world!');
比較長的標(biāo)識(shí)符或者數(shù)值, 不要為了讓代碼好看些而手工對(duì)齊. 如:
CORRECT_Object.prototype = { a: 0, b: 1, lengthyName: 2 };
不要這樣做:
WRONG_Object.prototype = { a : 0, b : 1, lengthyName: 2 };
函數(shù)參數(shù)
盡量讓函數(shù)參數(shù)在同一行上. 如果一行超過 80 字符, 每個(gè)參數(shù)獨(dú)占一行, 并以4個(gè)空格縮進(jìn), 或者與括號(hào)對(duì)齊, 以提高可讀性. 盡可能不要讓每行超過80個(gè)字符. 比如下面這樣:
// Four-space, wrap at 80. Works with very long function names, survives // renaming without reindenting, low on space. goog.foo.bar.doThingThatIsVeryDifficultToExplain = function( veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }; // Four-space, one argument per line. Works with long function names, // survives renaming, and emphasizes each argument. goog.foo.bar.doThingThatIsVeryDifficultToExplain = function( veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }; // Parenthesis-aligned indentation, wrap at 80. Visually groups arguments, // low on space. function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... } // Parenthesis-aligned, one argument per line. Visually groups and // emphasizes each individual argument. function bar(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }
傳遞匿名函數(shù)
如果參數(shù)中有匿名函數(shù), 函數(shù)體從調(diào)用該函數(shù)的左邊開始縮進(jìn)2個(gè)空格, 而不是從 function 這個(gè)關(guān)鍵字開始. 這讓匿名函數(shù)更加易讀 (不要增加很多沒必要的縮進(jìn)讓函數(shù)體顯示在屏幕的右側(cè)).
var names = items.map(function(item) { return item.name; }); prefix.something.reallyLongFunctionName('whatever', function(a1, a2) { if (a1.equals(a2)) { someOtherLongFunctionName(a1); } else { andNowForSomethingCompletelyDifferent(a2.parrot); } });
更多的縮進(jìn)
事實(shí)上, 除了 初始化數(shù)組和對(duì)象 , 和傳遞匿名函數(shù)外, 所有被拆開的多行文本要么選擇與之前的表達(dá)式左對(duì)齊, 要么以4個(gè)(而不是2個(gè))空格作為一縮進(jìn)層次.
someWonderfulHtml = '' + getEvenMoreHtml(someReallyInterestingValues, moreValues, evenMoreParams, 'a duck', true, 72, slightlyMoreMonkeys(0xfff)) + ''; thisIsAVeryLongVariableName = hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine(); thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() + thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore(); someValue = this.foo( shortArg, 'Some really long string arg - this is a pretty common case, actually.', shorty2, this.bar()); if (searchableCollection(allYourStuff).contains(theStuffYouWant) && !ambientNotification.isActive() && (client.isAmbientSupported() || client.alwaysTryAmbientAnyways()) { ambientNotification.activate(); }
空行
使用空行來劃分一組邏輯上相關(guān)聯(lián)的代碼片段.
doSomethingTo(x); doSomethingElseTo(x); andThen(x); nowDoSomethingWith(y); andNowWith(z);
二元和三元操作符
操作符始終跟隨著前行, 這樣就不用顧慮分號(hào)的隱式插入問題. 如果一行實(shí)在放不下, 還是按照上述的縮進(jìn)風(fēng)格來換行.
var x = a ? b : c; // All on one line if it will fit. // Indentation +4 is OK. var y = a ? longButSimpleOperandB : longButSimpleOperandC; // Indenting to the line position of the first operand is also OK. var z = a ? moreComplicatedB : moreComplicatedC;
括號(hào)
不要濫用括號(hào), 只在必要的時(shí)候使用它.
對(duì)于一元操作符(如delete
, typeof
和 void
), 或是在某些關(guān)鍵詞(如 return
, throw
, case
, new
)之后, 不要使用括號(hào).
字符串
單引號(hào) (') 優(yōu)于雙引號(hào) ("). 當(dāng)你創(chuàng)建一個(gè)包含 HTML 代碼的字符串時(shí)就知道它的好處了.
var msg = 'This is some HTML';
可見性 (私有域和保護(hù)域)
@private
和 @protected
JSDoc 的兩個(gè)標(biāo)記 @private
和 @protected
用來指明類, 函數(shù), 屬性的可見性域.
標(biāo)記為 @private
的全局變量和函數(shù), 表示它們只能在當(dāng)前文件中訪問.
標(biāo)記為 @private
的構(gòu)造器, 表示該類只能在當(dāng)前文件或是其靜態(tài)/普通成員中實(shí)例化; 私有構(gòu)造器的公共靜態(tài)屬性在當(dāng)前文件的任何地方都可訪問, 通過instanceof
操作符也可.
永遠(yuǎn)不要為 全局變量, 函數(shù), 構(gòu)造器加 @protected
標(biāo)記.
// File 1. // AA_PrivateClass_ and AA_init_ are accessible because they are global // and in the same file. /** * @private * @constructor */ AA_PrivateClass_ = function() { }; /** @private */ function AA_init_() { return new AA_PrivateClass_(); } AA_init_();
標(biāo)記為 @private
的屬性, 在當(dāng)前文件中可訪問它; 如果是類屬性私有, "擁有"該屬性的類的所有靜態(tài)/普通成員也可訪問, 但它們不能被不同文件中的子類訪問或覆蓋.
標(biāo)記為 @protected
的屬性, 在當(dāng)前文件中可訪問它, 如果是類屬性保護(hù), 那么"擁有"該屬性的類及其子類中的所有靜態(tài)/普通成員也可訪問.
注意: 這與 C++, Java 中的私有和保護(hù)不同, 它們是在當(dāng)前文件中, 檢查是否具有訪問私有/保護(hù)屬性的權(quán)限, 有權(quán)限即可訪問, 而不是只能在同一個(gè)類或類層次上. 而 C++ 中的私有屬性不能被子類覆蓋. (C++/Java 中的私有/保護(hù)是指作用域上的可訪問性, 在可訪問性上的限制. JS 中是在限制在作用域上. PS: 可見性是與作用域?qū)?yīng))
// File 1. /** @constructor */ AA_PublicClass = function() { }; /** @private */ AA_PublicClass.staticPrivateProp_ = 1; /** @private */ AA_PublicClass.prototype.privateProp_ = 2; /** @protected */ AA_PublicClass.staticProtectedProp = 31; /** @protected */ AA_PublicClass.prototype.protectedProp = 4; // File 2. /** * @return {number} The number of ducks we've arranged in a row. */ AA_PublicClass.prototype.method = function() { // Legal accesses of these two properties. return this.privateProp_ + AA_PublicClass.staticPrivateProp_; }; // File 3. /** * @constructor * @extends {AA_PublicClass} */ AA_SubClass = function() { // Legal access of a protected static property. AA_PublicClass.staticProtectedProp = this.method(); }; goog.inherits(AA_SubClass, AA_PublicClass); /** * @return {number} The number of ducks we've arranged in a row. */ AA_SubClass.prototype.method = function() { // Legal access of a protected instance property. return this.protectedProp; };
JavaScript 類型
如果使用 JSDoc, 那么盡量具體地, 準(zhǔn)確地根據(jù)它的規(guī)則來書寫類型說明. 目前支持兩種 JS2 和 JS1.x 類型規(guī)范.
JavaScript 類型語言
JS2 提議中包含了一種描述 JavaScript 類型的規(guī)范語法, 這里我們?cè)?JSDoc 中采用其來描述函數(shù)參數(shù)和返回值的類型.
JSDoc 的類型語言, 按照 JS2 規(guī)范, 也進(jìn)行了適當(dāng)改變, 但編譯器仍然支持舊語法.
名稱 | 語法 | 描述 | 棄用語法 |
---|---|---|---|
普通類型 | {boolean} , {Window} ,{goog.ui.Menu} | 普通類型的描述方法. | |
復(fù)雜類型 | {Array.<string>} 字符串?dāng)?shù)組.
{Object.<string, number>} 鍵為字符串, 值為整數(shù)的對(duì)象類型. | 參數(shù)化類型, 即指定了該類型中包含的一系列"類型參數(shù)". 類似于 Java 中的泛型. | |
聯(lián)合類型 | {(number|boolean)} 一個(gè)整數(shù)或者布爾值. | 表示其值可能是 A 類型, 也可能是 B 類型 | {(number,boolean)} ,{number|boolean} ,{(number||boolean)} |
記錄類型 | {{myNum: number, myObject}} 由現(xiàn)有類型組成的類型. | 表示包含指定成員及類型的值. 這個(gè)例子中, 注意大括號(hào)為類型語法的一部分. 比如, | |
可為空類型 | {?number} 一個(gè)整型數(shù)或者為 NULL | 表示一個(gè)值可能是 A 類型或者 null . 默認(rèn), 每個(gè)對(duì)象都是可為空的. 注意: 函數(shù)類型不可為空. | {number?} |
非空類型 | {!Object} 一個(gè)對(duì)象, 但絕不會(huì)是 null 值. | 說明一個(gè)值是類型 A 且肯定不是 null. 默認(rèn)情況下, 所有值類型 (boolean, number, string, 和 undefined) 不可為空. | {Object!} |
函數(shù)類型 | {function(string, boolean)} 具有兩個(gè)參數(shù) ( string 和 boolean) 的函數(shù)類型, 返回值未知. | 說明一個(gè)函數(shù). | |
函數(shù)返回類型 | {function(): number} 函數(shù)返回一個(gè)整數(shù). | 說明函數(shù)的返回類型. | |
函數(shù)的 this 類型 | {function(this:goog.ui.Menu, string)} 函數(shù)只帶一個(gè)參數(shù) (string), 并且在上下文 goog.ui.Menu 中執(zhí)行. | 說明函數(shù)類型的上下文類型. | |
可變參數(shù) | {function(string, ...[number]): number} 帶一個(gè)參數(shù) (字符類型) 的函數(shù)類型, 并且函數(shù)的參數(shù)個(gè)數(shù)可變, 但參數(shù)類型必須為 number. | 說明函數(shù)的可變長參數(shù). | |
可變長的參數(shù) (使用@param 標(biāo)記) | @param {...number} var_args 函數(shù)參數(shù)個(gè)數(shù)可變. | 使用標(biāo)記, 說明函數(shù)具有不定長參數(shù). | |
函數(shù)的 缺省參數(shù) | {function(?string=, number=)} 函數(shù)帶一個(gè)可空且可選的字符串型參數(shù), 一個(gè)可選整型參數(shù). = 語法只針對(duì)function 類型有效. | 說明函數(shù)的可選參數(shù). | |
函數(shù) 可選參數(shù) (使用@param 標(biāo)記) | @param {number=} opt_argument number 類型的可選參數(shù). | 使用標(biāo)記, 說明函數(shù)具有可選參數(shù). | |
所有類型 | {*} | 表示變量可以是任何類型. |
JavaScript中的類型
類型示例 | 值示例 | 描述 |
---|---|---|
number | 1 1.0 -5 1e5 Math.PI | |
Number | new Number(true) | Number 對(duì)象 |
string | 'Hello' "World" String(42) | 字符串值 |
String | new String('Hello') new String(42) | 字符串對(duì)象 |
boolean | true false Boolean(0) | 布爾值 |
Boolean | new Boolean(true) | 布爾對(duì)象 |
RegExp | new RegExp('hello') /world/g | |
Date | new Date new Date() | |
null | null | |
undefined | undefined | |
void | function f() { return; } | 沒有返回值 |
Array | ['foo', 0.3, null] [] | 類型不明確的數(shù)組 |
Array.<number> | [11, 22, 33] | 整型數(shù)組 |
Array.<Array.<string>> | [['one', 'two', 'three'], ['foo', 'bar']] | 字符串?dāng)?shù)組的數(shù)組 |
Object | {} {foo: 'abc', bar: 123, baz: null} | |
Object.<string> | {'foo': 'bar'} | 值為字符串的對(duì)象. |
Object.<number, string> | var obj = {}; obj[1] = 'bar'; | 鍵為整數(shù), 值為字符串的對(duì)象. 注意, JavaScript 中, 鍵總是被轉(zhuǎn)換成字符串, 所以 obj['1'] == obj[1] . 也所以, 鍵在 for...in 循環(huán)中是字符串類型. 但在編譯器中會(huì)明確根據(jù)鍵的類型來查找對(duì)象. |
Function | function(x, y) { return x * y; } | 函數(shù)對(duì)象 |
function(number, number): number | function(x, y) { return x * y; } | 函數(shù)值 |
SomeClass | /** @constructor */ function SomeClass() {} new SomeClass(); | |
SomeInterface | /** @interface */ function SomeInterface() {} SomeInterface.prototype.draw = function() {}; | |
project.MyClass | /** @constructor */ project.MyClass = function () {} new project.MyClass() | |
project.MyEnum | /** @enum {string} */ project.MyEnum = { BLUE: '#0000dd', RED: '#dd0000' }; | 枚舉 |
Element | document.createElement('div') | DOM 中的元素 |
Node | document.body.firstChild | DOM 中的節(jié)點(diǎn). |
HTMLInputElement | htmlDocument.getElementsByTagName('input')[0] | DOM 中, 特定類型的元素. |
JavaScript 是一種弱類型語言, 明白可選, 非空和未定義參數(shù)或?qū)傩灾g的細(xì)微差別還是很重要的.
對(duì)象類型(引用類型)默認(rèn)非空. 注意: 函數(shù)類型默認(rèn)不能為空. 除了字符串, 整型, 布爾, undefined 和 null 外, 對(duì)象可以是任何類型.
/** * Some class, initialized with a value. * @param {Object} value Some value. * @constructor */ function MyClass(value) { /** * Some value. * @type {Object} * @private */ this.myValue_ = value; }
告訴編譯器 myValue_
屬性為一對(duì)象或 null. 如果 myValue_
永遠(yuǎn)都不會(huì)為 null, 就應(yīng)該如下聲明:
/** * Some class, initialized with a non-null value. * @param {!Object} value Some value. * @constructor */ function MyClass(value) { /** * Some value. * @type {!Object} * @private */ this.myValue_ = value; }
這樣, 當(dāng)編譯器在代碼中碰到 MyClass
為 null 時(shí), 就會(huì)給出警告.
函數(shù)的可選參數(shù)可能在運(yùn)行時(shí)沒有定義, 所以如果他們又被賦給類屬性, 需要聲明成:
/** * Some class, initialized with an optional value. * @param {Object=} opt_value Some value (optional). * @constructor */ function MyClass(opt_value) { /** * Some value. * @type {Object|undefined} * @private */ this.myValue_ = opt_value; }
這告訴編譯器 myValue_
可能是一個(gè)對(duì)象, 或 null, 或 undefined.
注意: 可選參數(shù) opt_value
被聲明成 {Object=}
, 而不是 {Object|undefined}
. 這是因?yàn)榭蛇x參數(shù)可能是 undefined. 雖然直接寫 undefined 也并無害處, 但鑒于可閱讀性還是寫成上述的樣子.
最后, 屬性的非空和可選并不矛盾, 屬性既可是非空, 也可是可選的. 下面的四種聲明各不相同:
/** * Takes four arguments, two of which are nullable, and two of which are * optional. * @param {!Object} nonNull Mandatory (must not be undefined), must not be null. * @param {Object} mayBeNull Mandatory (must not be undefined), may be null. * @param {!Object=} opt_nonNull Optional (may be undefined), but if present, * must not be null! * @param {Object=} opt_mayBeNull Optional (may be undefined), may be null. */ function strangeButTrue(nonNull, mayBeNull, opt_nonNull, opt_mayBeNull) { // ... };
注釋
我們使用 JSDoc 中的注釋風(fēng)格. 行內(nèi)注釋使用 // 變量 的形式. 另外, 我們也遵循 C++ 代碼注釋風(fēng)格 . 這也就是說你需要:
- 版權(quán)和著作權(quán)的信息,
- 文件注釋中應(yīng)該寫明該文件的基本信息(如, 這段代碼的功能摘要, 如何使用, 與哪些東西相關(guān)), 來告訴那些不熟悉代碼的讀者.
- 類, 函數(shù), 變量和必要的注釋,
- 期望在哪些瀏覽器中執(zhí)行,
- 正確的大小寫, 標(biāo)點(diǎn)和拼寫.
為了避免出現(xiàn)句子片段, 請(qǐng)以合適的大/小寫單詞開頭, 并以合適的標(biāo)點(diǎn)符號(hào)結(jié)束這個(gè)句子.
現(xiàn)在假設(shè)維護(hù)這段代碼的是一位初學(xué)者. 這可能正好是這樣的!
目前很多編譯器可從 JSDoc 中提取類型信息, 來對(duì)代碼進(jìn)行驗(yàn)證, 刪除和壓縮. 因此, 你很有必要去熟悉正確完整的 JSDoc .
頂層/文件注釋
頂層注釋用于告訴不熟悉這段代碼的讀者這個(gè)文件中包含哪些東西. 應(yīng)該提供文件的大體內(nèi)容, 它的作者, 依賴關(guān)系和兼容性信息. 如下:
// Copyright 2009 Google Inc. All Rights Reserved. /** * @fileoverview Description of file, its uses and information * about its dependencies. * @author user@google.com (Firstname Lastname) */
類注釋
每個(gè)類的定義都要附帶一份注釋, 描述類的功能和用法. 也需要說明構(gòu)造器參數(shù). 如果該類繼承自其它類, 應(yīng)該使用 @extends
標(biāo)記. 如果該類是對(duì)接口的實(shí)現(xiàn), 應(yīng)該使用 @implements
標(biāo)記.
/** * 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);
方法與函數(shù)的注釋
提供參數(shù)的說明. 使用完整的句子, 并用第三人稱來書寫方法說明.
/** * 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) { // ... }
對(duì)于一些簡單的, 不帶參數(shù)的 getters, 說明可以忽略.
/** * @return {Element} The element for the component. */ goog.ui.Component.prototype.getElement = function() { return this.element_; };
屬性注釋
也需要對(duì)屬性進(jìn)行注釋.
/** * Maximum number of things per pane. * @type {number} */ project.MyClass.prototype.someProperty = 4;
類型轉(zhuǎn)換的注釋
有時(shí), 類型檢查不能很準(zhǔn)確地推斷出表達(dá)式的類型, 所以應(yīng)該給它添加類型標(biāo)記注釋來明確之, 并且必須在表達(dá)式和類型標(biāo)簽外面包裹括號(hào).
/** @type {number} */ (x) (/** @type {number} */ x)
JSDoc 縮進(jìn)
如果你在 @param
, @return
, @supported
, @this
或 @deprecated
中斷行, 需要像在代碼中一樣, 使用4個(gè)空格作為一個(gè)縮進(jìn)層次.
/** * 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
標(biāo)記中進(jìn)行縮進(jìn).
雖然不建議, 但也可對(duì)說明文字進(jìn)行適當(dāng)?shù)呐虐鎸?duì)齊. 不過, 這樣帶來一些負(fù)面影響, 就是當(dāng)你每次修改變量名時(shí), 都得重新排版說明文字以保持和變量名對(duì)齊.
/** * 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 };
注意一下, 枚舉也具有有效類型, 所以可以當(dāng)成參數(shù)類型來用.
/** * Sets project state. * @param {project.TriState} state New project state. */ project.setState = function(state) { // ... };
Typedefs
有時(shí)類型會(huì)很復(fù)雜. 比如下面的函數(shù), 接收 Element 參數(shù):
/** * @param {string} tagName * @param {(string|Element|Text|Array.<Element>|Array.<Text>)} contents * @return {Element} */ goog.createElement = function(tagName, contents) { ... };
你可以使用 @typedef
標(biāo)記來定義個(gè)常用的類型表達(dá)式.
/** @typedef {(string|Element|Text|Array.<Element>|Array.<Text>)} */ goog.ElementContent; /** * @param {string} tagName * @param {goog.ElementContent} contents * @return {Element} */ goog.createElement = function(tagName, contents) { ... };
JSDoc 標(biāo)記表
標(biāo)記 | 模板 & 例子 | 描述 | 類型檢測支持 |
---|---|---|---|
@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) { // ... }; | 給方法, 函數(shù), 構(gòu)造器中的參數(shù)添加說明. | 完全支持. |
@return | @return {Type} 描述 如: /** * @return {string} The hex ID of the last item. */ goog.Baz.prototype.getLastId = function() { // ... return id; }; | 給方法, 函數(shù)的返回值添加說明. 在描述布爾型參數(shù)時(shí), 用 "Whether the component is visible" 這種描述優(yōu)于 "True if the component is visible, false otherwise". 如果函數(shù)沒有返回值, 就不需要添加 @return 標(biāo)記. | 完全支持. |
@author | @author username@google.com (first last) 如: /** * @fileoverview Utilities for handling textareas. * @author kuth@google.com (Uthur Pendragon) */ | 表明文件的作者, 通常僅會(huì)在 @fileoverview 注釋中使用到它. | 不需要. |
@see | @see Link 如: /** * Adds a single item, recklessly. * @see #addSafely * @see goog.Collect * @see goog.RecklessAdder#add ... | 給出引用鏈接, 用于進(jìn)一步查看函數(shù)/方法的相關(guān)細(xì)節(jié). | 不需要. |
@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() { ... } | 指明類中的構(gòu)造器. | 會(huì)檢查. 如果省略了, 編譯器將禁止實(shí)例化. |
@interface | @interface 如: /** * A shape. * @interface */ function Shape() {}; Shape.prototype.draw = function() {}; /** * A polygon. * @interface * @extends {Shape} */ function Polygon() {}; Polygon.prototype.getSides = function() {}; | 指明這個(gè)函數(shù)是一個(gè)接口. | 會(huì)檢查. 如果實(shí)例化一個(gè)接口, 編譯器會(huì)警告. |
@type | @type Type @type {Type} 如: /** * The message hex ID. * @type {string} */ var hexId = hexId; | 標(biāo)識(shí)變量, 屬性或表達(dá)式的類型. 大多數(shù)類型是不需要加大括號(hào)的, 但為了保持一致, 建議統(tǒng)一加大括號(hào). | 會(huì)檢查 |
@extends | @extends Type @extends {Type} 如: /** * Immutable empty node list. * @constructor * @extends goog.ds.BasicNodeList */ goog.ds.EmptyNodeList = function() { ... }; | 與 @constructor 一起使用, 用來表明該類是擴(kuò)展自其它類的. 類型外的大括號(hào)可寫可不寫. | 會(huì)檢查 |
@implements | @implements Type @implements {Type} 如: /** * A shape. * @interface */ function Shape() {}; Shape.prototype.draw = function() {}; /** * @constructor * @implements {Shape} */ function Square() {}; Square.prototype.draw = function() { ... }; | 與 @constructor 一起使用, 用來表明該類實(shí)現(xiàn)自一個(gè)接口. 類型外的大括號(hào)可寫可不寫. | 會(huì)檢查. 如果接口不完整, 編譯器會(huì)警告. |
@lends | @lends objectName @lends {objectName} 如: goog.object.extend( Button.prototype, /** @lends {Button.prototype} */ { isButton: function() { return true; } }); | 表示把對(duì)象的鍵看成是其他對(duì)象的屬性. 該標(biāo)記只能出現(xiàn)在對(duì)象語法中. 注意, 括號(hào)中的名稱和其他標(biāo)記中的類型名稱不一樣, 它是一個(gè)對(duì)象名, 以"借過來"的屬性名命名. 如, @type {Foo} 表示 "Foo 的一個(gè)實(shí)例", but @lends {Foo} 表示 "Foo 構(gòu)造器". 更多有關(guān)此標(biāo)記的內(nèi)容見 JSDoc Toolkit docs. | 會(huì)檢查 |
@private | @private 如: /** * Handlers that are listening to this logger. * @type Array.<Function> * @private */ this.handlers_ = []; | 指明那些以下劃線結(jié)尾的方法和屬性是 私有的. 不推薦使用后綴下劃線, 而應(yīng)改用 @private. | 需要指定標(biāo)志來開啟. |
@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) { // ... }; | 指明接下來的方法和屬性是 被保護(hù)的. 被保護(hù)的方法和屬性的命名不需要以下劃線結(jié)尾, 和普通變量名沒區(qū)別. | 需要指定標(biāo)志來開啟. |
@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(); }); | 指明調(diào)用這個(gè)方法時(shí), 需要在哪個(gè)上下文中. 當(dāng) this 指向的不是原型方法的函數(shù)時(shí)必須使用這個(gè)標(biāo)記. | 會(huì)檢查 |
@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 }; | 用于枚舉類型. | 完全支持. 如果省略, 會(huì)認(rèn)為是整型. |
@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) { // ... }; | 告訴其他開發(fā)人員, 此方法, 函數(shù)已經(jīng)過時(shí), 不要再使用. 同時(shí)也會(huì)給出替代方法或函數(shù). | 不需要 |
@override | @override 如: /** * @return {string} Human-readable representation of project.SubClass. * @override */ project.SubClass.prototype.toString() { // ... }; | 指明子類的方法和屬性是故意隱藏了父類的方法和屬性. 如果子類的方法和屬性沒有自己的文檔, 就會(huì)繼承父類的. | 會(huì)檢查 |
@inheritDoc | @inheritDoc 如: /** @inheritDoc */ project.SubClass.prototype.toString() { // ... }; | 指明子類的方法和屬性是故意隱藏了父類的方法和屬性, 它們具有相同的文檔. 注意: 使用 @inheritDoc 意味著也同時(shí)使用了 @override. | 會(huì)檢查 |
@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: */ | 所有被標(biāo)記為 @license 或 @preserve 的, 會(huì)被編譯器保留不做任何修改而直接輸出到最終文擋中. 這個(gè)標(biāo)記讓一些重要的信息(如法律許可或版權(quán)信息)原樣保留, 同樣, 文本中的換行也會(huì)被保留. | 不需要. |
@noalias | @noalias 如: /** @noalias */ function Range() {} | 在外部文件中使用, 告訴編譯器不要為這個(gè)變量或函數(shù)重命名. | 不需要. |
@define | @define {Type} 描述 如: /** @define {boolean} */ var TR_FLAGS_ENABLE_DEBUG = true; /** @define {boolean} */ goog.userAgent.ASSUME_IE = false; | 表示該變量可在編譯時(shí)被編譯器重新賦值. 在上面例子中, BUILD 文件中指定了 --define='goog.userAgent.ASSUME_IE=true' 這個(gè)編譯之后, 常量 goog.userAgent.ASSUME_IE 將被全部直接替換為 true. | 不需要. |
@export | @export 如: /** @export */ foo.MyPublicClass.prototype.myPublicMethod = function() { // ... }; | 上面的例子代碼, 當(dāng)編譯器運(yùn)行時(shí)指定 --generate_exports 標(biāo)志, 會(huì)生成下面的代碼: goog.exportSymbol('foo.MyPublicClass.prototype.myPublicMethod', foo.MyPublicClass.prototype.myPublicMethod); 編譯后, 將源代碼中的名字原樣導(dǎo)出. 使用 @export 標(biāo)記時(shí), 應(yīng)該
| 不需要. |
@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'; | 聲明變量為只讀, 直接寫在一行上. 如果其他代碼中重寫該變量值, 編譯器會(huì)警告. 常量應(yīng)全部用大寫字符, 不過使用這個(gè)標(biāo)記, 可以幫你消除命名上依賴. 雖然 jsdoc.org 上列出的 @final 標(biāo)記作用等價(jià)于 @const , 但不建議使用. @const 與 JS1.5 中的 const 關(guān)鍵字一致. 注意, 編譯器不禁止修改常量對(duì)象的屬性(這與 C++ 中的常量定義不一樣). 如果可以準(zhǔn)確推測出常量類型的話, 那么類型申明可以忽略. 如果指定了類型, 應(yīng)該也寫在同一行上. 變量的額外注釋可寫可不寫. | 支持. |
@nosideeffects | @nosideeffects 如: /** @nosideeffects */ function noSideEffectsFn1() { // ... }; /** @nosideeffects */ var noSideEffectsFn2 = function() { // ... }; /** @nosideeffects */ a.prototype.noSideEffectsFn3 = function() { // ... }; | 用于對(duì)函數(shù)或構(gòu)造器聲明, 說明調(diào)用此函數(shù)不會(huì)有副作用. 編譯器遇到此標(biāo)記時(shí), 如果調(diào)用函數(shù)的返回值沒有其他地方使用到, 則會(huì)將這個(gè)函數(shù)整個(gè)刪除. | 不需要檢查. |
@typedef | @typedef 如: /** @typedef {(string|number)} */ goog.NumberLike; /** @param {goog.NumberLike} x A number or a string. */ goog.readNumber = function(x) { ... } | 這個(gè)標(biāo)記用于給一個(gè)復(fù)雜的類型取一個(gè)別名. | 會(huì)檢查 |
@externs | @externs 如: /** * @fileoverview This is an externs file. * @externs */ var document; | 指明一個(gè)外部文件. | 不會(huì)檢查 |
在第三方代碼中, 你還會(huì)見到其他一些 JSDoc 標(biāo)記. 這些標(biāo)記在 JSDoc Toolkit Tag Reference 都有介紹到, 但在 Google 的代碼中, 目前不推薦使用. 你可以認(rèn)為這些是將來會(huì)用到的 "保留" 名. 它們包含:
- @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 標(biāo)簽, 如 <code>, <pre>, <tt>, <strong>, <ul>, <ol>, <li>, <a>, 等等.
這就是說 JSDoc 不會(huì)完全依照純文本中書寫的格式. 所以, 不要在 JSDoc 中, 使用空白字符來做格式化:
/** * Computes weight based on three factors: * items sent * items received * last timestamp */
上面的注釋, 出來的結(jié)果是:
Computes weight based on three factors: items sent items received items received應(yīng)該這樣寫:
/** * Computes weight based on three factors: * <ul> * <li>items sent * <li>items received * <li>last timestamp * </ul> */
另外, 也不要包含任何 HTML 或類 HTML 標(biāo)簽, 除非你就想讓它們解析成 HTML 標(biāo)簽.
/** * Changes <b> tags to <span> tags. */
出來的結(jié)果是:
Changes tags to tags.另外, 也應(yīng)該在源代碼文件中讓其他人更可讀, 所以不要過于使用 HTML 標(biāo)簽:
/** * Changes <b> tags to <span> tags. */
上面的代碼中, 其他人就很難知道你想干嘛, 直接改成下面的樣子就清楚多了:
/** * Changes 'b' tags to 'span' tags. */
Tips and Tricks
True 和 False 布爾表達(dá)式
下面的布爾表達(dá)式都返回 false:
null
undefined
''
空字符串0
數(shù)字0
但小心下面的, 可都返回 true:
'0'
字符串0[]
空數(shù)組{}
空對(duì)象
下面段比較糟糕的代碼:
while (x != null) {
你可以直接寫成下面的形式(只要你希望 x 不是 0 和空字符串, 和 false):
while (x) {
如果你想檢查字符串是否為 null 或空:
if (y != null && y != '') {
但這樣會(huì)更好:
if (y) {
注意: 還有很多需要注意的地方, 如:
Boolean('0') == true
'0' != true0 != null
0 == []
0 == falseBoolean(null) == false
null != true
null != falseBoolean(undefined) == false
undefined != true
undefined != falseBoolean([]) == true
[] != true
[] == falseBoolean({}) == true
{} != true
{} != false
條件(三元)操作符 (?:)
三元操作符用于替代下面的代碼:
if (val != 0) { return foo(); } else { return bar(); }
你可以寫成:
return val ? foo() : bar();
在生成 HTML 代碼時(shí)也是很有用的:
var html = '<input type="checkbox"' + (isChecked ? ' checked' : '') + (isEnabled ? '' : ' disabled') + ' name="foo">';
&& 和 ||
二元布爾操作符是可短路的, 只有在必要時(shí)才會(huì)計(jì)算到最后一項(xiàng).
"||" 被稱作為 'default' 操作符, 因?yàn)榭梢赃@樣:
/** @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); }
不過這樣就有點(diǎn)兒過頭了:
node && node.kids && node.kids[index] && foo(node.kids[index]);
使用 join() 來創(chuàng)建字符串
通常是這樣使用的:
function listHtml(items) { var html = '<div class="foo">'; for (var i = 0; i < items.length; ++i) { if (i > 0) { html += ', '; } html += itemHtml(items[i]); } html += '</div>'; return html; }
但這樣在 IE 下非常慢, 可以用下面的方式:
function listHtml(items) { var html = []; for (var i = 0; i < items.length; ++i) { html[i] = itemHtml(items[i]); } return '<div class="foo">' + html.join(', ') + '</div>'; }
你也可以是用數(shù)組作為字符串構(gòu)造器, 然后通過 myArray.join('')
轉(zhuǎn)換成字符串. 不過由于賦值操作快于數(shù)組的 push()
, 所以盡量使用賦值操作.
遍歷 Node List
Node lists 是通過給節(jié)點(diǎn)迭代器加一個(gè)過濾器來實(shí)現(xiàn)的. 這表示獲取他的屬性, 如 length 的時(shí)間復(fù)雜度為 O(n), 通過 length 來遍歷整個(gè)列表需要 O(n^2).
var paragraphs = document.getElementsByTagName('p'); for (var i = 0; i < paragraphs.length; i++) { doSomething(paragraphs[i]); }
這樣做會(huì)更好:
var paragraphs = document.getElementsByTagName('p'); for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) { doSomething(paragraph); }
這種方法對(duì)所有的 collections 和數(shù)組(只要數(shù)組不包含 falsy 值) 都適用.
在上面的例子中, 也可以通過 firstChild 和 nextSibling 來遍歷孩子節(jié)點(diǎn).
var parentNode = document.getElementById('foo'); for (var child = parentNode.firstChild; child; child = child.nextSibling) { doSomething(child); }
Parting Words
保持一致性.
當(dāng)你在編輯代碼之前, 先花一些時(shí)間查看一下現(xiàn)有代碼的風(fēng)格. 如果他們給算術(shù)運(yùn)算符添加了空格, 你也應(yīng)該添加. 如果他們的注釋使用一個(gè)個(gè)星號(hào)盒子, 那么也請(qǐng)你使用這種方式.
代碼風(fēng)格中一個(gè)關(guān)鍵點(diǎn)是整理一份常用詞匯表, 開發(fā)者認(rèn)同它并且遵循, 這樣在代碼中就能統(tǒng)一表述. 我們?cè)谶@提出了一些全局上的風(fēng)格規(guī)則, 但也要考慮自身情況形成自己的代碼風(fēng)格. 但如果你添加的代碼和現(xiàn)有的代碼有很大的區(qū)別, 這就讓閱讀者感到很不和諧. 所以, 避免這種情況的發(fā)生.
修訂版 2.9
Aaron WhyteBob Jervis
Dan Pupius
Erik Arvidsson
Fritz Schneider
Robby Walker