摘要:綜述此文檔為谷歌基于代碼風格的完整定義。只有一篇文件遵守了以下規則的情況下,此文件可以被稱為遵從谷歌代碼風格。谷歌命名空間繼承關系聲明谷歌模塊聲明后可以再聲明命名空間繼承關系。
1.綜述
此文檔為谷歌基于JavaScript代碼風格的完整定義。只有一篇JavaScript文件遵守了以下規則的情況下,此文件可以被稱為遵從谷歌代碼風格。
正如其他谷歌代碼風格一樣,本文的跨度不僅包括了代碼格式的美觀,同樣也包括了代碼標準以及慣例。
在本文中,除特別注明之外:
術語“注釋”一般指的是實現注釋。我們不會使用“文檔注釋”,而作為代替,我們使用一般術語"JS注文(JSDoc)"說明在/** … */之中的人類可讀以及機器可讀的注釋。
當用到"必須","必須不","應該","不應該"以及"可以"的時候,本文遵從RFC 2119術語規則。其中,術語中的傾向于和避免與應該和不應該一致,而相應的,本文中命令與陳述式的說明則與術語中的必須相一致。
1.2 特別提示文中所有的示例代碼都是非標準的。換句話說,給出的谷歌代碼風格示例代碼都不是基于風格下實現這段代碼的唯一方法。示例代碼中給出的可選格式并不是需要強制執行的規則。
2.源文件基本規則 2.1 文件名文件名必須小寫,并且不可以含有除了下劃線(_)和破折號(-)之外的標點符號。請與你的項目風格和習慣保持一致。文件名的后綴必須是.js。
2.2 文件編碼:UTF-8所有源文件都必須在UTF-8編碼下。
2.3 特殊字符 2.3.1 空白字符除了換行符之外,在文件出現的空白字符只能是ASCII中的空格符(0x20)。這也就是說:
字符串中出現其他空白字符都會被轉義
制表符(Tab)不能用于縮進
2.3.2 特殊轉義字符任何含有特殊轉義字符的字符(", ", , 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碼字符,所以永遠不要讓你代碼因此而失去可讀性。
同樣,你的代碼也會因此而失敗需要修復。
一個多帶帶源文件必須依次含有以下信息:
許可與版權信息(如果需要)
文件總覽JS注文(@fileoverview JSDoc)(如果需要)
谷歌模塊(goog.module)聲明
谷歌引入文件(goog.require)聲明
文件正文
3.1 許可與版權信息(如果需要)如果文件有許可與版權信息,必須寫在文件結構的第一層。
3.2 文件總覽JS注文(@fileoverview JSDoc)(如果需要)具體格式見7.5節
3.3 谷歌模塊(goog.module)聲明所有文件都必須在多帶帶的一行中聲明作為谷歌模塊的名字:含有谷歌模塊命名的那一行不能換行,因此它會被排除在80列(字符)限制之外。
谷歌模塊命名的全部就是定義一個命名空間。它作為這個包的名字(用來映射該源文件在整個代碼資源目錄里位置的標識),同樣也可以代表該文件的主要類/枚舉類型/接口。
例如:
*goog.module("search.urlHistory.UrlHistoryService");*3.3.1 模塊的層級關系
永遠不要將一個命名空間定義為另一個命名空間的直系子空間。
不被允許的操作:
goog.module("foo.bar"); // "foo.bar.qux" would be fine, though goog.module("foo.bar.baz")*;
命名空間的層級關系代表了資源目錄的層級關系,因此低級子空間一定代表了高級父系資源目錄的子目錄。這也就說,因為在同一個資源目錄里,所以父系命名空間的所有者必須清楚的了解所有的子空間。
3.3.2 谷歌測試專用聲明(goog.setTestOnly)谷歌模塊聲明后可以再聲明為測試專用(goog.setTestOnly())。
3.3.3 谷歌命名空間繼承關系聲明(goog.module.declareLegacyNamespac)谷歌模塊聲明后可以再聲明命名空間繼承關系(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")也會有同樣的問題
3.3.4 ES6模塊因為到目前為止ES6模塊的語言還沒有完全完善,所以現在請不要使用ES6模塊(例如,關鍵詞export和import)。注意,當ES6模塊語言完全完善之后,就可以使用那些模塊了。
3.4 谷歌引入聲明(goog.require)在模塊聲明之后,引入的模塊通過谷歌引入(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"); // 必須在頂部(層) // … }3.4.1 谷歌前置聲明(goog.forwardDeclare)
前置聲明不常被用到,但是卻是用來處理循環依賴和引用后期加載代碼的有效方法。將所有前置聲明一起寫在引入聲明之后。谷歌前置聲明遵從和谷歌引入聲明一樣的規則。
3.5 文件的正文文件的正文現在所有依賴文件以后(中間至少隔一行)。
其中可以包括任何模塊內部聲明(常亮,變量,類,函數等等)和已引入的符號。
術語解釋:塊狀結構指的是類,函數,方法或者任何被大括號包住的內容里的代碼。值得注意的是,由于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;4.1.2 非空區塊:K&R風格
根據K&R風格對于非空區塊和非空塊狀結構中大括號的規定:
左大括號(前一個大括號)之前不空行
左大括號后新起一行
右大括號(后一個大括號)前新起一行
在函數,類,類的方法定義的右大括號之后新起一行,而在else, catch, while,逗號,分好,右小括號之后的右大括號不空行。
例如:
class InnerClass { constructor() {} /** @param {number} foo */ method(foo) { if (condition(foo)) { try { // Note: this might fail. something(); } catch (err) { recover(); } } } }4.1.3 空區塊:可簡化
空區塊和空塊狀結構開始之后可以直接結束,在{}中間不用任何字符,空格和換行,除非它是多區塊結構中的一部分(比如if/else/try/catch/finally)。
例如:
function doNothing() {} 不被允許的寫法: if (condition) { // … } else if (otherCondition) {} else { // … } try { // … } catch (e) {}4.2 區塊縮進:兩個空格
每當新開一個區塊或塊狀結構,增加兩個空格的縮進。區塊結束之后,縮進恢復到前一級水平。縮進對該區塊內的代碼和注釋同樣有效(見4.2節的例子)。
4.2.1 數組聲明:可作為塊狀結構任何數組都可以按塊狀結構的格式書寫。例如,以下的寫法都是有效(不代表全部寫法):
const a = [ 0, 1, 2, ]; const b = [0, 1, 2]; const c = [0, 1, 2]; someMethod(foo, [ 0, 1, 2, ], bar);
也可以使用其他組合,特別是用來強調元素之間的分組,而不是只用來減少大數組代碼中的垂直長度。
4.2.2 對象聲明:可作為塊狀結構任何對象都可以按塊狀結構的格式書寫,就像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);4.2.3 類的聲明
類的聲明(無論是內容聲明[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 {Foo4.2.4 函數表達式} */ 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(); } });4.2.5 Switch語句
就像其他塊狀結構,該語句的縮進方式也是+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"); }4.3 表達式 4.3.1 一個表達式一行
每一個表達式都要新起一行。
4.3.2 分號結尾每個表達式都要用分號結尾。禁止根據分號自動插入。
4.4 字符限制:80JavaScript有每行最多80字符的限制。除了下面列出的例外情況之外,每行的字符超過80個就會自動換行(具體規則見4.5節)
例外情況:
該行條件上不支持80字符限制的可能(一個很長的url,JS注文或者復制黏貼來的shell命令)
谷歌模塊(goog.module)和谷歌引入(goog.require)的聲明(見3.3節和3.4節)
4.5 自動換行(Line-wrapping)術語解釋:自動換行是指將一行的代碼分成幾行。
沒有固定的自動換行方法。通常來講,對于同一代碼有好幾種合法的自動換行的方法。
注意:雖然官方上自動換行的目的是規避每行的字符限制,但是在不違反字符限制的情況,文件作者也可以根據自己的判斷來自動換行。
小提示:精簡方法或者變量可以避免自動換行。
自動換行的第一要務:在更高語言優先級的位置斷句
更好的寫法:
currentEstimate = calc(currentEstimate + x * currentEstimate) / 2.0f;
不夠優秀的寫法:
currentEstimate = calc(currentEstimate + x * currentEstimate) / 2.0f;
在上面的例子中,語言優先級從高到底依次排列:表達式,除號,函數調用,參數,數字。
運算符換行規則:
請在運算符之后換行(注意這和JAVA谷歌代碼風格不同)。“.”并不是一個運算符,所以不適用上述規則。
方法和構造函數之后的左圓括號不能換行。
逗號緊跟前面的代碼。
注意:自動換行的主要目的是使代碼更清晰,代碼不一定要越短越好。
4.5.2 自動換行新的一行縮進至少+4除非根據縮進規則特別規定之外,自動換行后新的一行在原有的縮進水平上至少增加4個空格。
如果自動換行了多行,則可以適當的調整縮進水平。一般來講,自動換行新的一行縮進會基于4的倍數。只有同一層次的兩行才會用同樣的縮進水平。
在以下情況下會出現空行:
一個類或者對象聲明中的兩個函數或者方法之間。
例外情況:類中的兩個屬性聲明之間(中間沒有其他代碼)可以選擇性地空一行。這樣做可以對屬性進行邏輯分組。
方法中盡量少用空行的邏輯分組。函數主體的開頭和結尾不能空行。
一個類或者對象聲明中第一個方法之前或者最后一個方法之后可以選擇性地空行(既不推薦也不反對)
文件其他需要的位置(例如3.4節所寫的)
4.6.2 水平空白(空格)水平空白根據出現的位置不同分為三類:頭部,尾部,中間。頭部的空白(例如縮進)在本文的其他部分都已經解釋過了,而尾部的空白禁止出現。
除了其他規則特別規定以及常量,注釋,JS注文之外,中間部分的水平空白只可以在下列位置出現:
用來分隔保留關鍵詞(例如if, for, catch)和之后的左圓括號(()。
用來分隔右花括號(})和保留關鍵詞(例如else, catch)。
左花括號({)之前,但是有兩個例外:
a.數組字面量中一個函數的參數或者元素是對象字面量(例如foo({a: [{c:b.}])。
b.模板擴張中(例如abc${1 + 2}def。
在二元或者三元運算符的兩邊
在逗號和分號之后。注意,逗號和分號之前不能空格。
在對象字面量中的冒號之后。
在標識注釋的雙斜杠(//)的兩邊。這里可以使用多個空格(但是不是必須的)。
在JS注文(JSDoc)的開始標志之后和結束標志的兩邊(比如在簡易聲明或者造型定義中:this.foo = /** @type {number} */ (ba);*或者function(/** string */ foo) {)。
4.6.3 水平對齊:不被鼓勵的用法術語解釋:水平對齊是指在標記后加空格讓它定位到之前的標記的正下方。
這種寫法是允許的,但是谷歌代碼風格不鼓勵這種方法。在用到水平對齊的時候也甚至不需要保持使用它。
下面是一個不使用對齊和使用了對齊的例子,而后者是不被鼓勵的用法。
{ tiny: 42, // this is great longer: 435, // this too }; { tiny: 42, // permitted, but future edits longer: 435, // may leave it unaligned };
注意:對齊可以增加可讀性,但是對后續的維護增加了困難。考慮到后續改寫代碼可能只會該代碼中的一行。修改可能會導致規則允許下格式的崩壞。這常常會錯使代碼編寫者(比如你)調整附近幾行的空格,從而導致一系列的格式重寫。這樣,只是一行的修改就會有一個“爆炸半徑”(對附近代碼的影響)。這么做最多會讓你做一些無用功,但是至少是個失敗的歷史版本,降低了閱讀者的速度,也會導致一些合并沖突。
4.6.4 函數參數本規則更傾向于把所有函數的參數放在函數名的同一行。如果這么做讓代碼超出了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) { // … }4.7 分組括號(Grouping parentheses):推薦的寫法
分組括號也可以不加,在代碼編寫者或評定者都覺得這樣不會使代碼產生歧義而且不會影響代碼的可讀性的情況下。因為我們不能肯定每個代碼的閱讀者都記住了運算符優先表。
在delete, typeof, void, return, throw, case, in, of,或 yield的表達式之后不要加無用的圓括號。
類型造型時要加圓括號:/ @type {!Foo} / (foo)*。
這一節講的是實現注釋的規則。JS注文規則請見第7章。
4.8.1 塊注釋風格塊注釋遵從上下文一致的縮進規則。它們可以用/* … */ 和 //。對于多行注釋(/* … */),含有*的一行中的*必須與其他的對齊,以明確注釋沒有多出來的文本。如果方法和值的意義不明確,請在之后加上“參數名”注釋。
/* * This is * okay. */ // And so // is this. /* This is fine, too. */ someFunction(obviousParam, true /* shouldRender */, "hello" /* name */);
注釋不要包含在帶星號或者別的字符的框畫(boxes drawn)中。
不要在實現注釋中JS注文(/** … */)。
JavaScript有一些曖昧的語言特征(甚至有些危險)。這一章講述了哪些特征可以用,哪些特征不可以用,以及有限制的特征使用。
5.1 局部變量的聲明 5.1.1 使用const和let使用const或let來聲明局部變量。如果一個變量不會改變,默認用const,請不要使用var。
5.1.2 一條聲明只聲明一個變量一條聲明只聲明一個變量:請不要使用這樣的聲明,例如let a = 1, b = 2;。
5.1.3 聲明時盡量初始化我們不會常常在塊狀結構的頭部聲明局部變量。相反,我們會在第一次使用的附近來聲明(并初始化)它以減少代碼量。
5.1.4 需要注釋聲明類型在聲明的這一行或者上一行加上JS類型注文
例如:
const /** !Array5.2 數組字面量 5.2.1 用逗號結尾*/ data = []; /** @type {!Array } */ const data = [];
在最后一個元素和右方括號之間用逗號結尾,并分行。
例如:
const values = [ "first value", "second value", ];5.2.2 不要用變長數組構造函數(new)
當對代碼進行修改時,這種構造函數很容易出錯。
不被允許的寫法:
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)分配一個確定長度的數組是允許的。
5.2.3 非數字屬性數組不要定義或使用非數字屬性(除了length之外)。可以使用map或者Object替代。
5.2.4 解構賦值解構賦值時可以把數組字面量放在等式的左邊(比如從單一數組或者迭代中提取多個值)。在字面量的最后可以包含一個rest元素(緊更著...和變量名)。不用的元素應該省略。
const [a, b, c, ...rest] = generateResults(); let [, b,, d] = someArray;
解構賦值可以作為函數的參數(注意這里參數名可以省略)。在字面量里可以定義默認值。
/** @param {!Array=} param1 */ function optionalDestructuring([a = 4, b = 2] = []) { … };
不被允許的寫法:
function badDestructuring([a, b] = [4, 2]) { … };
盡量用對象字面量來把多個值封裝成一個函數參數或者返回值,因為這樣可以給元素命名并且聲明不同類型的元素。
5.2.5 展開運算符數組字面量可以用展開運算符(...)來折疊元素。展開運算符用于代替一個不好的構造器Array.prototype。展開運算符(...)后面不空格。
例如:
[...foo] // preferred over Array.prototype.slice.call(foo) [...foo, ...bar] // preferred over foo.concat(bar)5.3 對象字面量 5.3.1 用逗號結尾
在最后一個元素和右方括號之間用逗號結尾,并分行。
5.3.2 不要用Object構造器雖然Object構造器沒有Array構造器相同的問題。但是出于一致性的考慮仍然不允許用Object構造器。在這里用對象字面量({}或者{a: 0, b: 1, c: 2})。
5.3.3 帶引號鍵值和不帶引號鍵值不要混用結構體(含有不帶引號鍵值或符號)和字符文本結構(含有計算屬性或者帶引號鍵值),在同一個對象字面量中不要混用兩者。
不被允許的寫法:
{ a: 42, // struct-style unquoted key "b": 43, // dict-style quoted key }5.3.4 計算屬性命名
允許使用計算屬性(例如{["key" + foo()]: 42}),除非計算屬性鍵值是個Symbol類型(比如[]Symbol.iterator]),它會作為字符文本風格(帶引號)。也可以使用枚舉值作為計算屬性鍵值,但是在同一個字面量里不能與非枚舉值混用。
5.3.5 方法簡寫(速記方法)在對象字面量中方法可以用方法簡寫({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" }; } }5.3.6 屬性簡寫(速記屬性)
在對象字面量中允許屬性簡寫。
例如:
const foo = 1; const bar = 2; const obj = { foo, bar, method() { return this.foo + this.bar; }, }; assertEquals(3, obj.method());5.3.7 重構
在表達式等式的左邊可以用對象重構模式來重構對象或者從單個對象中提取多個值。
重構可以作為函數參數,但是應該保持盡量簡潔:不帶引號的速記屬性。更深層次的嵌套屬性和計算屬性盡量不要使用。重構參數盡量在左手邊定義默認值({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節)。
5.3.8 枚舉類型對象字面量加上@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, };5.4 類 5.4.1 構造
可以使用構造器來構造類。在設置域或者訪問this之前構造子類必須調用super()。接口中不可以調用構造函數。
5.4.2 域在構造器中設置所有實例的域(例如除了方法之外的所有屬性)。永不要用@const再指定注釋域。私有域必須帶@private的注釋,名字用下劃線結尾。不要用類的prototype設置域。
例如:
class Foo { constructor() { /** @private @const {!Bar} */ this.bar_ = computeBar(); } }
注意:構造器構造完成之后不要給一個實例添加或移除屬性,這樣會大大降低VM優化效果。如果一個域在定義的時候沒有初始化,那應該在構造器中把它設為undefined以防止之后類型轉變。對象中增加@struct會檢查未聲明的屬性不能訪問和增加。類默認有這個屬性。
5.4.3 計算屬性類中只有當屬性是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 themselves5.4.5 舊風格的類聲明
盡管我們推薦ES6的類,但是有些情況下ES6的類不可用。
例如:
如果存在或者即將存在子類,包括創建子類的框架,就不能立刻轉為ES6語法。如果這種情況下類使用了ES6語法,那么它下屬的所有子類都要用ES6語法規范。
因為ES6在調用super返回值之前無法訪問this實例,所以在調用超類構造器之前需要this的框架會出問題。
以下規則仍然使用:適當時應當使用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++; } }5.4.8 toString方法的覆蓋
方法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;5.5 函數 5.5.1 頂層函數
需要導出的函數可以直接定義在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 ... };5.5.2 嵌套函數和閉包
函數中可以嵌套函數,如果需要給函數命名,必須局部const定義。
5.5.3 箭頭函數箭頭函數語法簡潔,而且彌補了使用this的很多問題。比起使用關鍵詞function,我們更推薦箭頭函數,特別適用于嵌套函數(見5.3.5節)。
比起f.bind(this),特別是goog.bind(f, this),我們傾向于使用箭頭函數。避免const self = this的寫法。箭頭函數常常對于有可能會傳參的回調很有效。
箭頭的右邊可以是個表達式和塊狀結構。如果只有一個多帶帶的非解構參數,那么參數的圓括號可以省略。
注意:因為如果后期增加函數的參數,省略圓括號會使代碼出錯,所以即使箭頭函數只有一個參數,加上圓括號也是很好的做法。
生成器函數提供了一些有用的抽象方法,可以在需要的時候使用。
定義生成器函數時,在function后附上*,并與函數名空一格。當使用授權域的時候,在yield后加上*。
例如:
/** @return {!Iterator5.5.5 參數} */ function* gen1() { yield 42; } /** @return {!Iterator } */ const gen2 = function*() { yield* gen1(); } class SomeClass { /** @return {!Iterator } */ * 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 {!Array5.5.6 返回} array This is an ordinary parameter. * @param {...number} numbers The remainder of arguments are all numbers. */ function variadic(array, ...numbers) {}
在函數聲明之前的JS注文中必須明確定義返回值類型,除非是@override情況下所有類型都省略。
5.5.7 泛型定義泛型函數或方法時需在JS注文中加上@template TYPE。
5.5.8 展開運算符函數調用時可以使用展開運算符(...)。一個可變函數中一個數組或者它的迭代被分配成了多個參數時,比起Function.prototype.apply,我們更推薦展開運算符。
例如:
function myFunction(...elements) {} myFunction(...array, ...iterable, ...generator());5.6 字符串字面量 5.6.1 單引號
通常情況下,比起雙引號,我們更推薦單引號來修飾字符串字面量。
小提示:如果字符串中含有單引號,考慮使用模板字符串來避免解析錯誤。
通常情況下,字符串不能跨行。
使用模板字符串來處理復雜的字符串拼接,尤其當處理多條字符串字面量時。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92613.html
摘要:剛做完的一個項目里,為了切圖方便,接觸了下的腳本功能。這里解釋一下,也就是本文所討論的腳本,并不只有可以用,都是可以用的,不過需要調用各自不同的。一些腳本的線上參考資料常用部分漢化版常數表初識腳本留坑待續 剛做完的一個H5項目里,為了切圖方便,接觸了下Photoshop的腳本功能。從找資料、寫腳本到實際能用全套跑了一圈下來發現,嗯,果然是挺難用的[捂臉]。不過雖然缺點滿滿,但PS這個平...
摘要:在面向對象的語言中,比如,等,單例模式通常是定義類時將構造函數設為,保證對象不能在外部被出來,同時給類定義一個靜態的方法,用來獲取或者創建這個唯一的實例。 萬事開頭難,作為正經歷菜鳥賽季的前端player,已經忘記第一次告訴自己要寫一些東西出來是多久以的事情了。。。如果,你也和我一樣,那就像我一樣,從現在開始,從看到這篇文章開始,打開電腦,敲下你的第一篇文章(或者任何形式的文字)吧。 ...
原文 先說1.1總攬: Reactor模式 Reactor模式中的協調機制Event Loop Reactor模式中的事件分離器Event Demultiplexer 一些Event Demultiplexer處理不了的復雜I/O接口比如File I/O、DNS等 復雜I/O的解決方案 未完待續 前言 nodejs和其他編程平臺的區別在于如何去處理I/O接口,我們聽一個人介紹nodejs,總是...
閱讀 4150·2022-09-16 13:49
閱讀 1405·2021-11-22 15:12
閱讀 1525·2021-09-09 09:33
閱讀 1043·2019-08-30 13:15
閱讀 1728·2019-08-29 15:30
閱讀 662·2019-08-27 10:52
閱讀 2647·2019-08-26 17:41
閱讀 1901·2019-08-26 12:11