此文檔爲谷歌基於JavaScript代碼風格的完整定義。只有一篇JavaScript文件遵照了如下規則的狀況下,此文件能夠被稱爲聽從谷歌代碼風格。
正如其餘谷歌代碼風格同樣,本文的跨度不只包括了代碼格式的美觀,一樣也包括了代碼標準以及慣例。node
在本文中,除特別註明以外:python
/** … */
之中的人類可讀以及機器可讀的註釋。文中全部的示例代碼都是非標準的。換句話說,給出的谷歌代碼風格示例代碼都不是基於風格下實現這段代碼的惟一方法。示例代碼中給出的可選格式並非須要強制執行的規則。shell
文件名必須小寫,而且不能夠含有除了下劃線(_)和破折號(-)以外的標點符號。請與你的項目風格和習慣保持一致。文件名的後綴必須是.js。express
全部源文件都必須在UTF-8編碼下。數組
除了換行符以外,在文件出現的空白字符只能是ASCII中的空格符(0x20)。這也就是說:瀏覽器
任何含有特殊轉義字符的字符(', ", \, b, f, n, r, t, v)老是優先於相應的數字轉義字符(如x0a, u000a, u{a})。
永遠不要使用早已被捨棄的八進制轉義字符。安全
對於剩餘的非ASCII碼字符,不管是實際編碼字符(例如∞
),16進制字符或是轉義字符(例如\u221e
),只能在使代碼可讀性和可理解性更好的狀況下使用。
提示:在使用轉義字符的時候,或者甚至在使用實際編碼字符的某些時候,在後面加上一個解釋說明的註釋會有助於代碼的優雅和可讀性。
例如:閉包
const units = 'μs';(最優秀的寫法,即便沒有註釋也很明確清晰) const units = '\u03bcs'; // 'μs'(容許可是不被推薦) const units = '\u03bcs'; // Greek letter mu, 's'(容許可是簡陋而又易於出錯) const units = '\u03bcs';(失敗的寫法,使閱讀者難以理解) return '\ufeff' + content; // byte order mark(很好的寫法,用轉義字符來表示非輸出字符,後面必要時會有註釋)
提示:因爲某些程序可能沒法正確處理非ASCII碼字符,因此永遠不要讓你代碼所以而失去可讀性。
一樣,你的代碼也會所以而失敗須要修復。app
一個單獨源文件必須依次含有如下信息:框架
若是文件有許可與版權信息,必須寫在文件結構的第一層。
具體格式見7.5節
全部文件都必須在單獨的一行中聲明做爲谷歌模塊的名字:含有谷歌模塊命名的那一行不能換行,所以它會被排除在80列(字符)限制以外。
谷歌模塊命名的所有就是定義一個命名空間。它做爲這個包的名字(用來映射該源文件在整個代碼資源目錄裏位置的標識),一樣也能夠表明該文件的主要類/枚舉類型/接口。
例如:
*goog.module('search.urlHistory.UrlHistoryService');*
永遠不要將一個命名空間定義爲另外一個命名空間的直系子空間。
不被容許的操做:
goog.module('foo.bar'); // 'foo.bar.qux' would be fine, though goog.module('foo.bar.baz')*;
命名空間的層級關係表明了資源目錄的層級關係,所以低級子空間必定表明了高級父系資源目錄的子目錄。這也就說,由於在同一個資源目錄裏,因此父系命名空間的全部者必須清楚的瞭解全部的子空間。
谷歌模塊聲明後能夠再聲明爲測試專用(goog.setTestOnly())。
谷歌模塊聲明後能夠再聲明命名空間繼承關係(goog.module.declareLegacyNamespace)。(儘可能避免)
例如:
goog.module('my.test.helpers'); goog.module.declareLegacyNamespace(); goog.setTestOnly();
谷歌聲明命名空間繼承關係是用來簡易地過渡傳統面向對象層級關係命名,可是有一些命名上的限制。由於子系模塊的命名必定是在父系模塊以後,因此它必定不能是另外一個谷歌模塊的子系或者父系。(如:goog.module('parent);和goog.module('parent.child);同時存在會產生安全性問題goog.module('parent);goog.module('parent.child.grandchild')也會有一樣的問題
由於到目前爲止ES6模塊的語言尚未徹底完善,因此如今請不要使用ES6模塊(例如,關鍵詞export和import)。注意,當ES6模塊語言徹底完善以後,就可使用那些模塊了。
在模塊聲明以後,引入的模塊經過谷歌引入(goog.require)來聲明。每一個聲明的引入,都會分配一個固定的別名,或者拆分紅幾個固定的別名。在代碼和註釋裏,除了聲明引入的時候,這個別名是引入模塊的惟一合法指代,引入模塊的全名不能被使用。模塊引入的別名儘量地和該模塊全名經過點"."
拆分後的最後一部分的名字相一致,可是在可以避免歧義或者明顯增長代碼可讀性的狀況下,也能夠加上模塊全名的其餘部分。
若是隻是由於一個模塊的反作用(代碼意義的反作用,而非醫學意義,能夠理解爲"本不該有或者用戶意料以外的做用",或者簡單理解爲爲了不編譯器警告[warning]的做用)而引入它,能夠不用分配名字,可是在代碼的其餘地方不能出現該模塊的全名。
引入聲明前後排序規則:首先將帶名字的引入聲明按首字母排序列出,而後是解構(Destructuring)引入聲明按首字母排序列出,最後再把剩餘的引入聲明單獨列出(通常是用到反作用的引入模塊)。
提示:沒有必要徹底記住這個順序,而後嚴格按照這個順序排列。你能夠根據你的IDE來排列你的引入聲明,又是較長的模塊名或者別名會違反80字符(列)限制,因此這一行不能換行。換句話說,引入聲明的那幾行不會有80字符(列)限制。
例子:
const MyClass = goog.require('some.package.MyClass'); const NsMyClass = goog.require('other.ns.MyClass'); const googAsserts = goog.require('goog.asserts'); const testingAsserts = goog.require('goog.testing.asserts'); const than80columns = goog.require('pretend.this.is.longer.than80columns'); const {clear, forEach, map} = goog.require('goog.array'); /** @suppress {extraRequire} Initializes MyFramework. */ goog.require('my.framework.initialization');
不被容許的寫法:
const randomName = goog.require('something.else'); // 名字不匹配 const {clear, forEach, map} = // 不要換行 goog.require('goog.array'); function someFunction() { const alias = goog.require('my.long.name.alias'); // 必須在頂部(層) // … }
前置聲明不常被用到,可是倒是用來處理循環依賴和引用後期加載代碼的有效方法。將全部前置聲明一塊兒寫在引入聲明以後。谷歌前置聲明聽從和谷歌引入聲明同樣的規則。
文件的正文如今全部依賴文件之後(中間至少隔一行)。
其中能夠包括任何模塊內部聲明(常亮,變量,類,函數等等)和已引入的符號。
術語解釋:塊狀結構指的是類,函數,方法或者任何被大括號包住的內容裏的代碼。值得注意的是,因爲5.2小節和5.3小節的規定,數組和對象也能夠被當成塊狀結構。
小提示:推薦使用clang-format工具。JavaScript社區已經成功完成了clang-format對JavaScript語言的支持,其中也集成了幾位著名代碼開發者的努力。
全部流程控制結構(例如if, else, for, do, while等等)都須要使用大括號,哪怕其包含的主體代碼只有一條指令。第一條指令的非空塊狀結構必須另起一行。
不被容許的寫法:
if (someVeryLongCondition()) doSomething(); for (let i = 0; i < foo.length; i++) bar(foo[i]);
例外狀況:若是指令能夠徹底地用寫在一行裏,那麼能夠不用大括號以增長可讀性。如下的例子是流程控制結構裏惟一能夠不用大括號和空行的例子:
if (shortCondition()) return;
根據K&R風格對於非空區塊和非空塊狀結構中大括號的規定:
例如:
class InnerClass { constructor() {} /** @param {number} foo */ method(foo) { if (condition(foo)) { try { // Note: this might fail. something(); } catch (err) { recover(); } } } }
空區塊和空塊狀結構開始以後能夠直接結束,在{}中間不用任何字符,空格和換行,除非它是多區塊結構中的一部分(好比if/else/try/catch/finally)。
例如:
function doNothing() {} 不被容許的寫法: if (condition) { // … } else if (otherCondition) {} else { // … } try { // … } catch (e) {}
每當新開一個區塊或塊狀結構,增長兩個空格的縮進。區塊結束以後,縮進恢復到前一級水平。縮進對該區塊內的代碼和註釋一樣有效(見4.2節的例子)。
任何數組均可以按塊狀結構的格式書寫。例如,如下的寫法都是有效(不表明所有寫法):
const a = [ 0, 1, 2, ]; const b = [0, 1, 2]; const c = [0, 1, 2]; someMethod(foo, [ 0, 1, 2, ], bar);
也可使用其餘組合,特別是用來強調元素之間的分組,而不是隻用來減小大數組代碼中的垂直長度。
任何對象均可以按塊狀結構的格式書寫,就像4.2.1節的例子。例如,如下的寫法都是有效(不表明所有寫法):
const a = { a: 0, b: 1, }; const b = {a: 0, b: 1}; const c = {a: 0, b: 1}; someMethod(foo, { a: 0, b: 1, }, bar);
類的聲明(不管是內容聲明[declarations]仍是表達式聲明[expressions])都像塊狀結構同樣縮進。在類的方法聲明和類中的內容聲明(表達式結束時仍然須要加分號)的右大括號(後一個大括號)以後不加分號。其中可使用關鍵字extends,可是不要用@extends的JS註文(JSDoc),除非你繼承了一個模板類型(templatized type)。
例如:
class Foo { constructor() { /** @type {number} */ this.x = 42; } /** @return {number} */ method() { return this.x; } } Foo.Empty = class {}; /** @extends {Foo<string>} */ foo.Bar = class extends Foo { /** @override */ method() { return super.method() / 2; } }; /** @interface */ class Frobnicator { /** @param {string} message */ frobnicate(message) {} }
當聲明匿名函數時,函數正文在原有縮進水平上增長兩個空格的縮進。
例子:
prefix.something.reallyLongFunctionName('whatever', (a1, a2) => { // Indent the function body +2 relative to indentation depth // of the 'prefix' statement one line above. if (a1.equals(a2)) { someOtherLongFunctionName(a1); } else { andNowForSomethingCompletelyDifferent(a2.parrot); } }); some.reallyLongFunctionCall(arg1, arg2, arg3) .thatsWrapped() .then((result) => { // Indent the function body +2 relative to the indentation depth // of the '.then()' call. if (result) { result.use(); } });
就像其餘塊狀結構,該語句的縮進方式也是+2。
開始新的一條Switch標籤,格式要像開始一個新的塊狀結構,新起一行,縮進+2。適當時候能夠用塊狀結構來明確Switch全文範圍。
而到下一條Switch標籤的開始行,縮進(暫時)還原到原縮進水平。
在break
和下一條Switch標籤之間能夠適當地空一行。
例子:
switch (animal) { case Animal.BANDERSNATCH: handleBandersnatch(); break; case Animal.JABBERWOCK: handleJabberwock(); break; default: throw new Error('Unknown animal'); }
每個表達式都要新起一行。
每一個表達式都要用分號結尾。禁止根據分號自動插入。
JavaScript有每行最多80字符的限制。除了下面列出的例外狀況以外,每行的字符超過80個就會自動換行(具體規則見4.5節)
例外狀況:
術語解釋:自動換行是指將一行的代碼分紅幾行。
沒有固定的自動換行方法。一般來說,對於同一代碼有好幾種合法的自動換行的方法。
注意:雖然官方上自動換行的目的是規避每行的字符限制,可是在不違反字符限制的狀況,文件做者也能夠根據本身的判斷來自動換行。
小提示:精簡方法或者變量能夠避免自動換行。
自動換行的第一要務:在更高語言優先級的位置斷句
更好的寫法:
currentEstimate = calc(currentEstimate + x * currentEstimate) / 2.0f;
不夠優秀的寫法:
currentEstimate = calc(currentEstimate + x * currentEstimate) / 2.0f;
在上面的例子中,語言優先級從高到底依次排列:表達式,除號,函數調用,參數,數字。
運算符換行規則:
注意:自動換行的主要目的是使代碼更清晰,代碼不必定要越短越好。
除非根據縮進規則特別規定以外,自動換行後新的一行在原有的縮進水平上至少增長4個空格。
若是自動換行了多行,則能夠適當的調整縮進水平。通常來說,自動換行新的一行縮進會基於4的倍數。只有同一層次的兩行纔會用一樣的縮進水平。
在如下狀況下會出現空行:
例外狀況:類中的兩個屬性聲明之間(中間沒有其餘代碼)能夠選擇性地空一行。這樣作能夠對屬性進行邏輯分組。
水平空白根據出現的位置不一樣分爲三類:頭部,尾部,中間。頭部的空白(例如縮進)在本文的其餘部分都已經解釋過了,而尾部的空白禁止出現。
除了其餘規則特別規定以及常量,註釋,JS註文以外,中間部分的水平空白只能夠在下列位置出現:
(
)。}
)和保留關鍵詞(例如else, catch)。{
)以前,可是有兩個例外: a.數組字面量中一個函數的參數或者元素是對象字面量(例如foo({a: [{c:b.}])。
b.模板擴張中(例如abc${1 + 2}
def。
//
)的兩邊。這裏可使用多個空格(可是不是必須的)。this.foo = /** @type {number} */ (ba
);*或者function(/** string */ foo) {
)。術語解釋:水平對齊是指在標記後加空格讓它定位到以前的標記的正下方。
這種寫法是容許的,可是谷歌代碼風格不鼓勵這種方法。在用到水平對齊的時候也甚至不須要保持使用它。
下面是一個不使用對齊和使用了對齊的例子,然後者是不被鼓勵的用法。
{ tiny: 42, // this is great longer: 435, // this too }; { tiny: 42, // permitted, but future edits longer: 435, // may leave it unaligned };
注意:對齊能夠增長可讀性,可是對後續的維護增長了困難。考慮到後續改寫代碼可能只會該代碼中的一行。修改可能會致使規則容許下格式的崩壞。這經常會錯使代碼編寫者(好比你)調整附近幾行的空格,從而致使一系列的格式重寫。這樣,只是一行的修改就會有一個「爆炸半徑」(對附近代碼的影響)。這麼作最多會讓你作一些無用功,可是至少是個失敗的歷史版本,下降了閱讀者的速度,也會致使一些合併衝突。
本規則更傾向於把全部函數的參數放在函數名的同一行。若是這麼作讓代碼超出了80字符的限制,那麼就必須作基於可讀性的自動換行。爲了節約空間,最好每行都接近80字符,或者一個參數一行來增長可讀性。縮進4個空格。容許和圓括號對齊,可是不推薦。
下列就是最多見的函數參數對齊模式:
// Arguments start on a new line, indented four spaces. Preferred when the // arguments don't fit on the same line with the function name (or the keyword // "function") but fit entirely on the second line. Works with very long // function names, survives renaming without reindenting, low on space. doSomething( descriptiveArgumentOne, descriptiveArgumentTwo, descriptiveArgumentThree) { // … } // If the argument list is longer, wrap at 80. Uses less vertical space, // but violates the rectangle rule and is thus not recommended. doSomething(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // … } // Four-space, one argument per line. Works with long function names, // survives renaming, and emphasizes each argument. doSomething( veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // … }
分組括號也能夠不加,在代碼編寫者或評定者都以爲這樣不會使代碼產生歧義並且不會影響代碼的可讀性的狀況下。由於咱們不能確定每一個代碼的閱讀者都記住了運算符優先表。
在delete, typeof, void, return, throw, case, in, of,或 yield的表達式以後不要加無用的圓括號。
類型造型時要加圓括號:/ @type {!Foo} / (foo)*。
這一節講的是實現註釋的規則。JS註文規則請見第7章。
塊註釋聽從上下文一致的縮進規則。它們能夠用/* … */ 和 //
。對於多行註釋(/* … */
),含有*
的一行中的*
必須與其餘的對齊,以明確註釋沒有多出來的文本。若是方法和值的意義不明確,請在以後加上「參數名」註釋。
/* * This is * okay. */ // And so // is this. /* This is fine, too. */ someFunction(obviousParam, true /* shouldRender */, 'hello' /* name */);
註釋不要包含在帶星號或者別的字符的框畫(boxes drawn)中。
不要在實現註釋中JS註文(/** … */
)。
JavaScript有一些曖昧的語言特徵(甚至有些危險)。這一章講述了哪些特徵能夠用,哪些特徵不能夠用,以及有限制的特徵使用。
使用const或let來聲明局部變量。若是一個變量不會改變,默認用const,請不要使用var。
一條聲明只聲明一個變量:請不要使用這樣的聲明,例如let a = 1, b = 2;。
咱們不會經常在塊狀結構的頭部聲明局部變量。相反,咱們會在第一次使用的附近來聲明(並初始化)它以減小代碼量。
在聲明的這一行或者上一行加上JS類型註文
例如:
const /** !Array<number> */ data = []; /** @type {!Array<number>} */ const data = [];
在最後一個元素和右方括號之間用逗號結尾,並分行。
例如:
const values = [ 'first value', 'second value', ];
當對代碼進行修改時,這種構造函數很容易出錯。
不被容許的寫法:
const a1 = new Array(x1, x2, x3); const a2 = new Array(x1, x2); const a3 = new Array(x1); const a4 = new Array()
第三個例子會出現異常:若是x1
純數字,a3
就會是個共有x1
個undefined
元素的數組,若是x1
是個其餘數字,系統會拋出異常,若是x1
是個其餘類型,a3
會是個單元素的數組。
應該這麼寫:
const a1 = [x1, x2, x3]; const a2 = [x1, x2]; const a3 = [x1]; const a4 = [];
可是用 new Array(length)
分配一個肯定長度的數組是容許的。
數組不要定義或使用非數字屬性(除了length以外)。可使用map或者Object替代。
解構賦值時能夠把數組字面量放在等式的左邊(好比從單一數組或者迭代中提取多個值)。在字面量的最後能夠包含一個rest元素(緊更着...
和變量名)。不用的元素應該省略。
const [a, b, c, ...rest] = generateResults(); let [, b,, d] = someArray;
解構賦值能夠做爲函數的參數(注意這裏參數名能夠省略)。在字面量裏能夠定義默認值。
/** @param {!Array<number>=} param1 */ function optionalDestructuring([a = 4, b = 2] = []) { … };
不被容許的寫法:
function badDestructuring([a, b] = [4, 2]) { … };
儘可能用對象字面量來把多個值封裝成一個函數參數或者返回值,由於這樣能夠給元素命名而且聲明不一樣類型的元素。
數組字面量能夠用展開運算符(...)
來摺疊元素。展開運算符用於代替一個很差的構造器Array.prototype。展開運算符(...)
後面不空格。
例如:
[...foo] // preferred over Array.prototype.slice.call(foo) [...foo, ...bar] // preferred over foo.concat(bar)
在最後一個元素和右方括號之間用逗號結尾,並分行。
Object
構造器雖然Object
構造器沒有Array
構造器相同的問題。可是出於一致性的考慮仍然不容許用Object
構造器。在這裏用對象字面量({}
或者{a: 0, b: 1, c: 2}
)。
結構體(含有不帶引號鍵值或符號)和字符文本結構(含有計算屬性或者帶引號鍵值),在同一個對象字面量中不要混用二者。
不被容許的寫法:
{ a: 42, // struct-style unquoted key 'b': 43, // dict-style quoted key }
容許使用計算屬性(例如{['key' + foo()]: 42}),除非計算屬性鍵值是個Symbol類型(好比[]Symbol.iterator]),它會做爲字符文本風格(帶引號)。也可使用枚舉值做爲計算屬性鍵值,可是在同一個字面量裏不能與非枚舉值混用。
在對象字面量中方法能夠用方法簡寫({method() {… }}),其中用一個function或一個箭向函數字面量來代替冒號。
例如:
return { stuff: 'candy', method() { return this.stuff; // Returns 'candy' }, };
注意在方法簡寫或者function
中的this
指的是對象字面量自己,可是箭向函數中的this
指向的是對象字面量外面的域。
例如:
class { getObjectLiteral() { this.stuff = 'fruit'; return { stuff: 'candy', method: () => this.stuff, // Returns 'fruit' }; } }
在對象字面量中容許屬性簡寫。
例如:
const foo = 1; const bar = 2; const obj = { foo, bar, method() { return this.foo + this.bar; }, }; assertEquals(3, obj.method());
在表達式等式的左邊能夠用對象重構模式來重構對象或者從單個對象中提取多個值。
重構能夠做爲函數參數,可是應該保持儘可能簡潔:不帶引號的速記屬性。更深層次的嵌套屬性和計算屬性儘可能不要使用。重構參數儘可能在左手邊定義默認值({str = 'some default'} = {}優於{str} = {str: 'some default'}),而後若是一個重構對象是它本身,必須默認設爲{}
。JS註文能夠給重構參數一個名字(不會用到可是能夠被編譯器接受)。
例如:
/** * @param {string} ordinary * @param {{num: (number|undefined), str: (string|undefined)}=} param1 * num: The number of times to do something. * str: A string to do stuff to. */ function destructured(ordinary, {num, str = 'some default'} = {})
不被容許的寫法:
/** @param {{x: {num: (number|undefined), str: (string|undefined)}}} param1 */ function nestedTooDeeply({x: {num, str}}) {}; /** @param {{num: (number|undefined), str: (string|undefined)}=} param1 */ function nonShorthandProperty({num: a, str: b} = {}) {}; /** @param {{a: number, b: number}} param1 */ function computedKey({a, b, [a + b]: c}) {}; /** @param {{a: number, b: string}=} param1 */ function nontrivialDefault({a, b} = {a: 2, b: 4}) {};
谷歌模塊引入goog.require也能夠重構,這一行不能換行。整個代碼不管多長都必須放在一行(見3.4節)。
對象字面量加上@enum註釋以後能夠定義枚舉類型。一旦定義完成後,就不能再給枚舉類型增長屬性了。枚舉類型必須保持不變,枚舉類型中的值也不能更改。
/** * Supported temperature scales. * @enum {string} */ const TemperatureScale = { CELSIUS: 'celsius', FAHRENHEIT: 'fahrenheit', }; /** * An enum with two options. * @enum {number} */ const Option = { /** The option used shall have been the first. */ FIRST_OPTION: 1, /** The second among two options. */ SECOND_OPTION: 2, };
可使用構造器來構造類。在設置域或者訪問this以前構造子類必須調用super()。接口中不能夠調用構造函數。
在構造器中設置全部實例的域(例如除了方法以外的全部屬性)。永不要用@const再指定註釋域。私有域必須帶@private的註釋,名字用下劃線結尾。不要用類的prototype設置域。
例如:
class Foo { constructor() { /** @private @const {!Bar} */ this.bar_ = computeBar(); } }
注意:構造器構造完成以後不要給一個實例添加或移除屬性,這樣會大大下降VM優化效果。若是一個域在定義的時候沒有初始化,那應該在構造器中把它設爲undefined以防止以後類型轉變。對象中增長@struct會檢查未聲明的屬性不能訪問和增長。類默認有這個屬性。
類中只有當屬性是Symbol類型的時候才能使用計算屬性。不容許使用文本屬性(如5.3.3節中間定義,也就是帶引號或者非符號的計算屬性)。任何類均可以定義可迭代的[Symbol.iterator]的方法。
注意:使用其餘內置Symbol的時候要注意不要被編譯器填充致使其餘瀏覽器失效。
在不影響可讀性的狀況,咱們更推薦使用模型內置局部方法,而不是私有靜態方法。
靜態方法應該只在基類中被訪問。靜態方法不能訪問包含本構造器或者子類構造器(若是這麼作,必須在定義含有@nocollapse)建立實例的變量,也不能在沒有定義過該方法的子類中調用。
不被容許的寫法:
class Base { /** @nocollapse */ static foo() {} } class Sub extends Base {} function callFoo(cls) { cls.foo(); } // discouraged: don't call static methods dynamically Sub.foo(); // illegal: don't call static methods on subclasses that don't define it themselves
儘管咱們推薦ES6的類,可是有些狀況下ES6的類不可用。
例如:
如下規則仍然使用:適當時應當使用let,const,默認參數(缺省參數),rest參數和箭頭函數。
可使用goog.defineClass來模擬一個相似ES6的類聲明:
let C = goog.defineClass(S, { /** * @param {string} value */ constructor(value) { S.call(this, 2); /** @const */ this.prop = value; }, /** * @param {string} param * @return {number} */ method(param) { return 0; }, });
另外,儘管goog.defineClass更推薦用新語法代碼,可是也容許使用相對傳統語法的代碼。
/** * @constructor @extends {S} * @param {string} value */ function C(value) { S.call(this, 2); /** @const */ this.prop = value; } goog.inherits(C, S); /** * @param {string} param * @return {number} */ C.prototype.method = function(param) { return 0; };
若是有超類,應在超類構造器被調用以後在本構造器中定義實例的屬性。方法應在構造器的prototype定義。
正肯定義構造器的prototype層級是很困難的。全部,最好使用the Closure Library 中的goog.inherits。
關鍵詞class能夠定義比prototype更清晰和可讀性更好的類。常規實現代碼不須要操做這些對象,儘管這樣作能夠定義5.4.5節中所說的@record接口和類。不容許混入和修改嵌入對象的prototype。
例外:代碼框架(例如Polymer或者Angular)有時須要用到prototype,以免求助於更不推薦的工做區。
例外2:定義接口中的類(見5.4.9節)
請不要使用JavaScript中的getter函數與setter函數。它們會產生潛在的危險和困難,並且只被部分編譯器支持。建議使用常規方法來代替。
例外:當使用數據封裝的框架(例如Polymer或者Angular),能夠少許地使用getter函數與setter函數。可是請注意,這些方法只被部分編譯器支持。使用的時候請在數組或者對象字面量中加上get foo()或者set foo(value)定義,若是作不到這些,請加上Object.defineProperties。請不要使用會重命名接口屬性的Object.defineProperty。getter函數必定不能改變顯狀態。
不被容許的寫法:
class Foo { get next() { return this.nextId++; } }
方法toString能夠被覆蓋,可是定義的方法必定要能無反作用的運行。
特別值得注意的是,在toString中調用其餘方法有可能致使無限循環的異常狀況。
接口能夠用@interface或@record來聲明。@record聲明的接口能夠被類和對象字面量顯式實現(例如經過@implements),也能夠隱式實現。
在接口中的全部非靜態方法都必須爲空區塊。在接口主體以後必須定義域做爲prototype上留的底。
例如:
/** * Something that can frobnicate. * @record */ class Frobnicator { /** * Performs the frobnication according to the given strategy. * @param {!FrobnicationStrategy} strategy */ frobnicate(strategy) {} } /** @type {number} The number of attempts before giving up. */ Frobnicator.prototype.attempts;
須要導出的函數能夠直接定義在exports對象中,也能夠局部定義而後單獨導出。咱們推薦使用不導出的函數,不要加上@private定義。
例如:
/** @return {number} */ function helperFunction() { return 42; } /** @return {number} */ function exportedFunction() { return helperFunction() * 2; } /** * @param {string} arg * @return {number} */ function anotherExportedFunction(arg) { return helperFunction() / arg.length; } /** @const */ exports = {exportedFunction, anotherExportedFunction}; /** @param {string} arg */ exports.foo = (arg) => { // do some stuff ... };
函數中能夠嵌套函數,若是須要給函數命名,必須局部const定義。
箭頭函數語法簡潔,並且彌補了使用this的不少問題。比起使用關鍵詞function,咱們更推薦箭頭函數,特別適用於嵌套函數(見5.3.5節)。
比起f.bind(this),特別是goog.bind(f, this),咱們傾向於使用箭頭函數。避免const self = this的寫法。箭頭函數經常對於有可能會傳參的回調頗有效。
箭頭的右邊能夠是個表達式和塊狀結構。若是隻有一個單獨的非解構參數,那麼參數的圓括號能夠省略。
注意:由於若是後期增長函數的參數,省略圓括號會使代碼出錯,因此即便箭頭函數只有一個參數,加上圓括號也是很好的作法。
生成器函數提供了一些有用的抽象方法,能夠在須要的時候使用。
定義生成器函數時,在function後附上*
,並與函數名空一格。當使用受權域的時候,在yield後加上*
。
例如:
/** @return {!Iterator<number>} */ function* gen1() { yield 42; } /** @return {!Iterator<number>} */ const gen2 = function*() { yield* gen1(); } class SomeClass { /** @return {!Iterator<number>} */ * gen() { yield 42; } }
函數參數必須在JS註文中預約義格式化,除非使用了@override,全部類型省略。
內聯參數類型必須在參數名前特別說明(好比/ number / foo, / string / bar) => foo + bar)。內聯類型註釋和@param類型註釋在同一個函數聲明時不能混用。
在參數列中可選參數容許使用等號操做符。就像必須參數同樣寫可選參數(好比不加opt_前綴),等號兩邊需加空格。在JS註文可選參數必須在類型聲明中加上=
後綴,不要用初始化以確保代碼明確。就算可選參數的默認值是undefined,也要在函數聲明中聲明默認值。
例如:
/** * @param {string} required This parameter is always needed. * @param {string=} optional This parameter can be omitted. * @param {!Node=} node Another optional parameter. */ function maybeDoSomething(required, optional = '', node = undefined) {}
儘可能少用默認參數。當有較多非天然語序可選參數時,咱們更推薦重構(見5.3.7節)來建立可讀性更好的API。
注意:不一樣於python的缺省參數,容許使用返回新的可變對象(好比{}和[]
)的初始化模塊由於它會預先設定每次都使用默認值,因此調用之間對象不會共享。
小提示:包括函數調用的任何表達式都會用到初始化模塊,因此初始化模塊應該儘可能簡單。避免初始化模塊暴露共享可變域,這容易致使函數調用之間的無心耦合。
用剩餘參數來代替訪問arguments。在JS註文中剩餘參數須要加一個...
前綴。剩餘參數必須在參數列表的最後。在參數名和...
之間不要空格。不要把它命名成var_args,也不要用arguments
來命名參數或局部變量,以免和內置名的混淆。
例如:
/** * @param {!Array<string>} array This is an ordinary parameter. * @param {...number} numbers The remainder of arguments are all numbers. */ function variadic(array, ...numbers) {}
在函數聲明以前的JS註文中必須明肯定義返回值類型,除非是@override狀況下全部類型都省略。
定義泛型函數或方法時需在JS註文中加上@template TYPE。
函數調用時可使用展開運算符(...
)。一個可變函數中一個數組或者它的迭代被分配成了多個參數時,比起Function.prototype.apply,咱們更推薦展開運算符。
例如:
function myFunction(...elements) {} myFunction(...array, ...iterable, ...generator());
一般狀況下,比起雙引號,咱們更推薦單引號來修飾字符串字面量。
小提示:若是字符串中含有單引號,考慮使用模板字符串來避免解析錯誤。
一般狀況下,字符串不能跨行。
使用模板字符串來處理複雜的字符串拼接,尤爲當處理多條字符串字面量時。