Google JavaScript 編碼規范指南
修訂版: 2.9
Aaron WhyteBob Jervis
Dan Pupius
Eric Arvidsson
Fritz Schneider
Robby Walker
每個條目都有概述信息, 點擊 ▽ 查看詳細的內容. 你也可以點擊下面的按鈕
重要注意事項
背景
JavaScript 是一種客戶端腳本語言, Google 的許多開源工程中都有用到它. 這份指南列出了編寫 JavaScript 時需要遵守的規則.
JavaScript 語言規范
變量
var
關鍵字.Decision:當你沒有寫 var
, 變量就會暴露在全局上下文中, 這樣很可能會和現有變量沖突. 另外, 如果沒有加上, 很難明確該變量的作用域是什么, 變量也很可能像在局部作用域中, 很輕易地泄漏到 Document 或者 Window 中, 所以務必用 var
去聲明變量.
常量
NAMES_LIKE_THIS
, 即使用大寫字符, 并用下劃線分隔. 你也可用 @const
標記來指明它是一個常量. 但請永遠不要使用 const
關鍵詞.Decision:
對于基本類型的常量, 只需轉換命名.
/** * The number of seconds in a minute. * @type {number} */ goog.example.SECONDS_IN_A_MINUTE = 60;
對于非基本類型, 使用 @const
標記.
/** * 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 }
這標記告訴編譯器它是常量.
至于關鍵詞 const
, 因為 IE 不能識別, 所以不要使用.
分號
如果僅依靠語句間的隱式分隔, 有時會很麻煩. 你自己更能清楚哪里是語句的起止.
而且有些情況下, 漏掉分號會很危險:
// 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();
這段代碼會發生些什么詭異事呢?
- 報 JavaScript 錯誤 - 例子1上的語句會解釋成, 一個函數帶一匿名函數作為參數而被調用, 返回42后, 又一次被"調用", 這就導致了錯誤.
- 例子2中, 你很可能會在運行時遇到 'no such property in undefined' 錯誤, 原因是代碼試圖這樣
x[ffVersion][isIE]()
執行. - 當
resultOfOperation()
返回非 NaN 時, 就會調用die
, 其結果也會賦給THINGS_TO_EAT
.
為什么?
JavaScript 的語句以分號作為結束符, 除非可以非常準確推斷某結束位置才會省略分號. 上面的幾個例子產出錯誤, 均是在語句中聲明了函數/對象/數組直接量, 但 閉括號('}'或']')并不足以表示該語句的結束. 在 JavaScript 中, 只有當語句后的下一個符號是后綴或括號運算符時, 才會認為該語句的結束.
遺漏分號有時會出現很奇怪的結果, 所以確保語句以分號結束.
塊內函數聲明
不要寫成:
if (x) { function foo() {} }
雖然很多 JS 引擎都支持塊內聲明函數, 但它不屬于 ECMAScript 規范 (見 ECMA-262, 第13和14條). 各個瀏覽器糟糕的實現相互不兼容, 有些也與未來 ECMAScript 草案相違背. ECMAScript 只允許在腳本的根語句或函數中聲明函數. 如果確實需要在塊中定義函數, 建議使用函數表達式來初始化變量:
if (x) { var foo = function() {} }
自定義異常
有時發生異常了, 但返回的錯誤信息比較奇怪, 也不易讀. 雖然可以將含錯誤信息的引用對象或者可能產生錯誤的完整對象傳遞過來, 但這樣做都不是很好, 最好還是自定義異常類, 其實這些基本上都是最原始的異常處理技巧. 所以在適當的時候使用自定義異常.
標準特性
最大化可移植性和兼容性, 盡量使用標準方法而不是用非標準方法, (比如, 優先用string.charAt(3)
而不用 string[3]
, 通過 DOM 原生函數訪問元素, 而不是使用應用封裝好的快速接口.
封裝基本類型
沒有任何理由去封裝基本類型, 另外還存在一些風險:
var x = new Boolean(false); if (x) { alert('hi'); // Shows 'hi'. }
除非明確用于類型轉換, 其他情況請千萬不要這樣做!
var x = Boolean(0); if (x) { alert('hi'); // This will never be alerted. } typeof Boolean(0) == 'boolean'; typeof new Boolean(0) == 'object';
有時用作 number
, string
或 boolean
時, 類型的轉換會非常實用.
多級原型結構
多級原型結構是指 JavaScript 中的繼承關系. 當你自定義一個D類, 且把B類作為其原型, 那么這就獲得了一個多級原型結構. 這些原型結構會變得越來越復雜!
使用 the Closure 庫 中的 goog.inherits()
或其他類似的用于繼承的函數, 會是更好的選擇.
function D() { goog.base(this) } goog.inherits(D, B); D.prototype.method = function() { ... };
方法定義
Foo.prototype.bar = function() { ... };
有很多方法可以給構造器添加方法或成員, 我們更傾向于使用如下的形式:
Foo.prototype.bar = function() { /* ... */ };
閉包
閉包也許是 JS 中最有用的特性了. 有一份比較好的介紹閉包原理的文檔.
有一點需要牢記, 閉包保留了一個指向它封閉作用域的指針, 所以, 在給 DOM 元素附加閉包時, 很可能會產生循環引用, 進一步導致內存泄漏. 比如下面的代碼:
function foo(element, a, b) { element.onclick = function() { /* uses a and b */ }; }
這里, 即使沒有使用 element
, 閉包也保留了 element
, a
和 b
的引用, . 由于 element
也保留了對閉包的引用, 這就產生了循環引用, 這就不能被 GC 回收. 這種情況下, 可將代碼重構為:
function foo(element, a, b) { element.onclick = bar(a, b); } function bar(a, b) { return function() { /* uses a and b */ } }
eval()
eval()
會讓程序執行的比較混亂, 當 eval()
里面包含用戶輸入的話就更加危險. 可以用其他更佳的, 更清晰, 更安全的方式寫你的代碼, 所以一般情況下請不要使用 eval(). 當碰到一些需要解析序列化串的情況下(如, 計算 RPC 響應), 使用 eval
很容易實現.
解析序列化串是指將字節流轉換成內存中的數據結構. 比如, 你可能會將一個對象輸出成文件形式:
users = [ { name: 'Eric', id: 37824, email: 'jellyvore@myway.com' }, { name: 'xtof', id: 31337, email: 'b4d455h4x0r@google.com' }, ... ];
很簡單地調用 eval
后, 把表示成文件的數據讀取回內存中.
類似的, eval()
對 RPC 響應值進行解碼. 例如, 你在使用 XMLHttpRequest
發出一個 RPC 請求后, 通過 eval () 將服務端的響應文本轉成 JavaScript 對象:
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
讓你的代碼在語義上變得不清晰. 因為 with
的對象, 可能會與局部變量產生沖突, 從而改變你程序原本的用義. 下面的代碼是干嘛的?
with (foo) { var x = 3; return x; }
答案: 任何事. 局部變量 x
可能被 foo
的屬性覆蓋, 當它定義一個 setter 時, 在賦值 3
后會執行很多其他代碼. 所以不要使用 with
語句.
this
this
的語義很特別. 有時它引用一個全局對象(大多數情況下), 調用者的作用域(使用 eval
時), DOM 樹中的節點(添加事件處理函數時), 新創建的對象(使用一個構造器), 或者其他對象(如果函數被 call()
或 apply()
).
使用時很容易出錯, 所以只有在下面兩個情況時才能使用:
- 在構造器中
- 對象的方法(包括創建的閉包)中
for-in 循環
對 Array
用 for-in
循環有時會出錯. 因為它并不是從 0
到 length - 1
進行遍歷, 而是所有出現在對象及其原型鏈的鍵值. 下面就是一些失敗的使用案例:
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.
而遍歷數組通常用最普通的 for 循環.
function printArray(arr) { var l = arr.length; for (var i = 0; i < l; i++) { print(arr[i]); } }
關聯數組
Array
作為 map/hash/associative 數組.數組中不允許使用非整型作為索引值, 所以也就不允許用關聯數組. 而取代它使用 Object
來表示 map/hash 對象. Array
僅僅是擴展自 Object
(類似于其他 JS 中的對象, 就像 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.';
在編譯時, 不能忽略行起始位置的空白字符; "\" 后的空白字符會產生奇怪的錯誤; 雖然大多數腳本引擎支持這種寫法, 但它不是 ECMAScript 的標準規范.
Array 和 Object 直接量
使用 Array
和 Object
語法, 而不使用 Array
和 Object
構造器.
使用 Array 構造器很容易因為傳參不恰當導致錯誤.
// 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();
如果傳入一個參數而不是2個參數, 數組的長度很有可能就不是你期望的數值了.
為了避免這些歧義, 我們應該使用更易讀的直接量來聲明.
var a = [x1, x2, x3]; var a2 = [x1, x2]; var a3 = [x1]; var a4 = [];
雖然 Object 構造器沒有上述類似的問題, 但鑒于可讀性和一致性考慮, 最好還是在字面上更清晰地指明.
var o = new Object(); var o2 = new Object(); o2.a = 0; o2.b = 1; o2.c = 2; o2['strange key'] = 3;
應該寫成:
var o = {}; var o2 = { a: 0, b: 1, c: 2, 'strange key': 3 };
修改內置對象的原型
千萬不要修改內置對象, 如 Object.prototype
和 Array.prototype
的原型. 而修改內置對象, 如 Function.prototype
的原型, 雖然少危險些, 但仍會導致調試時的詭異現象. 所以也要避免修改其原型.
IE下的條件注釋
不要這樣子寫:
var f = function () { /*@cc_on if (@_jscript) { return 2* @*/ 3; /*@ } @*/ };
條件注釋妨礙自動化工具的執行, 因為在運行時, 它們會改變 JavaScript 語法樹.
JavaScript 編碼風格
命名
通常, 使用 functionNamesLikeThis
, variableNamesLikeThis
, ClassNamesLikeThis
, EnumNamesLikeThis
, methodNamesLikeThis
, 和SYMBOLIC_CONSTANTS_LIKE_THIS
.
展開見細節.
屬性和方法
- 文件或類中的 私有 屬性, 變量和方法名應該以下劃線 "_" 開頭.
- 保護 屬性, 變量和方法名不需要下劃線開頭, 和公共變量名一樣.
更多有關 私有 和 保護的信息見, visibility.
方法和函數參數
可選參數以 opt_
開頭.
函數的參數個數不固定時, 應該添加最后一個參數 var_args
為參數的個數. 你也可以不設置 var_args
而取代使用 arguments
.
可選和可變參數應該在 @param
標記中說明清楚. 雖然這兩個規定對編譯器沒有任何影響, 但還是請盡量遵守
Getters 和 Setters
Getters 和 setters 并不是必要的. 但只要使用它們了, 就請將 getters 命名成 getFoo()
形式, 將 setters 命名成 setFoo(value)
形式. (對于布爾類型的 getters, 使用 isFoo()
也可.)
命名空間
JavaScript 不支持包和命名空間.
不容易發現和調試全局命名的沖突, 多個系統集成時還可能因為命名沖突導致很嚴重的問題. 為了提高 JavaScript 代碼復用率, 我們遵循下面的約定以避免沖突.
為全局代碼使用命名空間
在全局作用域上, 使用一個唯一的, 與工程/庫相關的名字作為前綴標識. 比如, 你的工程是 "Project Sloth", 那么命名空間前綴可取為 sloth.*
.
var sloth = {}; sloth.sleep = function() { ... };
許多 JavaScript 庫, 包括 the Closure Library and Dojo toolkit 為你提供了聲明你自己的命名空間的函數. 比如:
goog.provide('sloth'); sloth.sleep = function() { ... };
明確命名空間所有權
當選擇了一個子命名空間, 請確保父命名空間的負責人知道你在用哪個子命名空間, 比如說, 你為工程 'sloths' 創建一個 'hats' 子命名空間, 那確保 Sloth 團隊人員知道你在使用 sloth.hats
.
外部代碼和內部代碼使用不同的命名空間
"外部代碼" 是指來自于你代碼體系的外部, 可以獨立編譯. 內外部命名應該嚴格保持獨立. 如果你使用了外部庫, 他的所有對象都在 foo.hats.*
下, 那么你自己的代碼不能在 foo.hats.*
下命名, 因為很有可能其他團隊也在其中命名.
foo.require('foo.hats'); /** * WRONG -- Do NOT do this. * @constructor * @extend {foo.hats.RoundHat} */ foo.hats.BowlerHat = function() { };
如果你需要在外部命名空間中定義新的 API, 那么你應該直接導出一份外部庫, 然后在這份代碼中修改. 在你的內部代碼中, 應該通過他們的內部名字來調用內部 API , 這樣保持一致性可讓編譯器更好的優化你的代碼.
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()); };
不要對命名空間創建別名.
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); };
不要在全局范圍內創建別名, 而僅在函數塊作用域中使用.
文件名
文件名應該使用小寫字符, 以避免在有些系統平臺上不識別大小寫的命名方式. 文件名以.js
結尾, 不要包含除 -
和 _
外的標點符號(使用 -
優于 _
).
自定義 toString() 方法
可自定義 toString()
方法, 但確保你的實現方法滿足: (1) 總是成功 (2) 沒有其他負面影響. 如果不滿足這兩個條件, 那么可能會導致嚴重的問題, 比如, 如果 toString()
調用了包含 assert
的函數, assert
輸出導致失敗的對象, 這在 toString()
也會被調用.
明確作用域
任何時候都要明確作用域 - 提高可移植性和清晰度. 例如, 不要依賴于作用域鏈中的 window
對象. 可能在其他應用中, 你函數中的 window
不是指之前的那個窗口對象.
代碼格式化
主要依照C++ 格式規范 ( 中文版 ), 針對 JavaScript, 還有下面一些附加說明.
大括號
分號會被隱式插入到代碼中, 所以你務必在同一行上插入大括號. 例如:
if (something) { // ... } else { // ... }
數組和對象的初始化
如果初始值不是很長, 就保持寫在單行上:
var arr = [1, 2, 3]; // No space after [ or before ]. var obj = {a: 1, b: 2, c: 3}; // No space after { or before }.
初始值占用多行時, 縮進2個空格.
// 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!');
比較長的標識符或者數值, 不要為了讓代碼好看些而手工對齊. 如:
CORRECT_Object.prototype = { a: 0, b: 1, lengthyName: 2 };
不要這樣做:
WRONG_Object.prototype = { a : 0, b : 1, lengthyName: 2 };
函數參數
盡量讓函數參數在同一行上. 如果一行超過 80 字符, 每個參數獨占一行, 并以4個空格縮進, 或者與括號對齊, 以提高可讀性. 盡可能不要讓每行超過80個字符. 比如下面這樣:
// 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) { // ... }
傳遞匿名函數
如果參數中有匿名函數, 函數體從調用該函數的左邊開始縮進2個空格, 而不是從 function 這個關鍵字開始. 這讓匿名函數更加易讀 (不要增加很多沒必要的縮進讓函數體顯示在屏幕的右側).
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); } });
更多的縮進
事實上, 除了 初始化數組和對象 , 和傳遞匿名函數外, 所有被拆開的多行文本要么選擇與之前的表達式左對齊, 要么以4個(而不是2個)空格作為一縮進層次.
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(); }
空行
使用空行來劃分一組邏輯上相關聯的代碼片段.
doSomethingTo(x); doSomethingElseTo(x); andThen(x); nowDoSomethingWith(y); andNowWith(z);
二元和三元操作符
操作符始終跟隨著前行, 這樣就不用顧慮分號的隱式插入問題. 如果一行實在放不下, 還是按照上述的縮進風格來換行.
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;
括號
不要濫用括號, 只在必要的時候使用它.
對于一元操作符(如delete
, typeof
和 void
), 或是在某些關鍵詞(如 return
, throw
, case
, new
)之后, 不要使用括號.
可見性 (私有域和保護域)
@private
和 @protected
JSDoc 的兩個標記 @private
和 @protected
用來指明類, 函數, 屬性的可見性域.
標記為 @private
的全局變量和函數, 表示它們只能在當前文件中訪問.
標記為 @private
的構造器, 表示該類只能在當前文件或是其靜態/普通成員中實例化; 私有構造器的公共靜態屬性在當前文件的任何地方都可訪問, 通過instanceof
操作符也可.
永遠不要為 全局變量, 函數, 構造器加 @protected
標記.
// 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_();
標記為 @private
的屬性, 在當前文件中可訪問它; 如果是類屬性私有, "擁有"該屬性的類的所有靜態/普通成員也可訪問, 但它們不能被不同文件中的子類訪問或覆蓋.
標記為 @protected
的屬性, 在當前文件中可訪問它, 如果是類屬性保護, 那么"擁有"該屬性的類及其子類中的所有靜態/普通成員也可訪問.
注意: 這與 C++, Java 中的私有和保護不同, 它們是在當前文件中, 檢查是否具有訪問私有/保護屬性的權限, 有權限即可訪問, 而不是只能在同一個類或類層次上. 而 C++ 中的私有屬性不能被子類覆蓋. (C++/Java 中的私有/保護是指作用域上的可訪問性, 在可訪問性上的限制. JS 中是在限制在作用域上. PS: 可見性是與作用域對應)
// 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, 那么盡量具體地, 準確地根據它的規則來書寫類型說明. 目前支持兩種 JS2 和 JS1.x 類型規范.
JavaScript 類型語言
JS2 提議中包含了一種描述 JavaScript 類型的規范語法, 這里我們在 JSDoc 中采用其來描述函數參數和返回值的類型.
JSDoc 的類型語言, 按照 JS2 規范, 也進行了適當改變, 但編譯器仍然支持舊語法.
名稱 | 語法 | 描述 | 棄用語法 |
---|---|---|---|
普通類型 | {boolean} , {Window} ,{goog.ui.Menu} | 普通類型的描述方法. | |
復雜類型 | {Array.<string>} 字符串數組.
{Object.<string, number>} 鍵為字符串, 值為整數的對象類型. | 參數化類型, 即指定了該類型中包含的一系列"類型參數". 類似于 Java 中的泛型. | |
聯合類型 | {(number|boolean)} 一個整數或者布爾值. | 表示其值可能是 A 類型, 也可能是 B 類型 | {(number,boolean)} ,{number|boolean} ,{(number||boolean)} |
記錄類型 | {{myNum: number, myObject}} 由現有類型組成的類型. | 表示包含指定成員及類型的值. 這個例子中, 注意大括號為類型語法的一部分. 比如, | |
可為空類型 | {?number} 一個整型數或者為 NULL | 表示一個值可能是 A 類型或者 null . 默認, 每個對象都是可為空的. 注意: 函數類型不可為空. | {number?} |
非空類型 | {!Object} 一個對象, 但絕不會是 null 值. | 說明一個值是類型 A 且肯定不是 null. 默認情況下, 所有值類型 (boolean, number, string, 和 undefined) 不可為空. | {Object!} |
函數類型 | {function(string, boolean)} 具有兩個參數 ( string 和 boolean) 的函數類型, 返回值未知. | 說明一個函數. | |
函數返回類型 | {function(): number} 函數返回一個整數. | 說明函數的返回類型. | |
函數的 this 類型 | {function(this:goog.ui.Menu, string)} 函數只帶一個參數 (string), 并且在上下文 goog.ui.Menu 中執行. | 說明函數類型的上下文類型. | |
可變參數 | {function(string, ...[number]): number} 帶一個參數 (字符類型) 的函數類型, 并且函數的參數個數可變, 但參數類型必須為 number. | 說明函數的可變長參數. | |
可變長的參數 (使用@param 標記) | @param {...number} var_args 函數參數個數可變. | 使用標記, 說明函數具有不定長參數. | |
函數的 缺省參數 | {function(?string=, number=)} 函數帶一個可空且可選的字符串型參數, 一個可選整型參數. = 語法只針對function 類型有效. | 說明函數的可選參數. | |
函數 可選參數 (使用@param 標記) | @param {number=} opt_argument number 類型的可選參數. | 使用標記, 說明函數具有可選參數. | |
所有類型 | {*} | 表示變量可以是任何類型. |
JavaScript中的類型
類型示例 | 值示例 | 描述 |
---|---|---|
number | 1 1.0 -5 1e5 Math.PI | |
Number | new Number(true) | Number 對象 |
string | 'Hello' "World" String(42) | 字符串值 |
String | new String('Hello') new String(42) | 字符串對象 |
boolean | true false Boolean(0) | 布爾值 |
Boolean | new Boolean(true) | 布爾對象 |
RegExp | new RegExp('hello') /world/g | |
Date | new Date new Date() | |
null | null | |
undefined | undefined | |
void | function f() { return; } | 沒有返回值 |
Array | ['foo', 0.3, null] [] | 類型不明確的數組 |
Array.<number> | [11, 22, 33] | 整型數組 |
Array.<Array.<string>> | [['one', 'two', 'three'], ['foo', 'bar']] | 字符串數組的數組 |
Object | {} {foo: 'abc', bar: 123, baz: null} | |
Object.<string> | {'foo': 'bar'} | 值為字符串的對象. |
Object.<number, string> | var obj = {}; obj[1] = 'bar'; | 鍵為整數, 值為字符串的對象. 注意, JavaScript 中, 鍵總是被轉換成字符串, 所以 obj['1'] == obj[1] . 也所以, 鍵在 for...in 循環中是字符串類型. 但在編譯器中會明確根據鍵的類型來查找對象. |
Function | function(x, y) { return x * y; } | 函數對象 |
function(number, number): number | function(x, y) { return x * y; } | 函數值 |
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 中的節點. |
HTMLInputElement | htmlDocument.getElementsByTagName('input')[0] | DOM 中, 特定類型的元素. |
JavaScript 是一種弱類型語言, 明白可選, 非空和未定義參數或屬性之間的細微差別還是很重要的.
對象類型(引用類型)默認非空. 注意: 函數類型默認不能為空. 除了字符串, 整型, 布爾, undefined 和 null 外, 對象可以是任何類型.
/** * Some class, initialized with a value. * @param {Object} value Some value. * @constructor */ function MyClass(value) { /** * Some value. * @type {Object} * @private */ this.myValue_ = value; }
告訴編譯器 myValue_
屬性為一對象或 null. 如果 myValue_
永遠都不會為 null, 就應該如下聲明:
/** * 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; }
這樣, 當編譯器在代碼中碰到 MyClass
為 null 時, 就會給出警告.
函數的可選參數可能在運行時沒有定義, 所以如果他們又被賦給類屬性, 需要聲明成:
/** * 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_
可能是一個對象, 或 null, 或 undefined.
注意: 可選參數 opt_value
被聲明成 {Object=}
, 而不是 {Object|undefined}
. 這是因為可選參數可能是 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 中的注釋風格. 行內注釋使用 // 變量 的形式. 另外, 我們也遵循 C++ 代碼注釋風格 . 這也就是說你需要:
- 版權和著作權的信息,
- 文件注釋中應該寫明該文件的基本信息(如, 這段代碼的功能摘要, 如何使用, 與哪些東西相關), 來告訴那些不熟悉代碼的讀者.
- 類, 函數, 變量和必要的注釋,
- 期望在哪些瀏覽器中執行,
- 正確的大小寫, 標點和拼寫.
為了避免出現句子片段, 請以合適的大/小寫單詞開頭, 并以合適的標點符號結束這個句子.
現在假設維護這段代碼的是一位初學者. 這可能正好是這樣的!
目前很多編譯器可從 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 };
注意一下, 枚舉也具有有效類型, 所以可以當成參數類型來用.
/** * 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
標記來定義個常用的類型表達式.
/** @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 標記表
標記 | 模板 & 例子 | 描述 | 類型檢測支持 |
---|---|---|---|
@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. | 會檢查 |
@private | @private 如: /** * Handlers that are listening to this logger. * @type Array.<Function> * @private */ this.handlers_ = []; | 指明那些以下劃線結尾的方法和屬性是 私有的. 不推薦使用后綴下劃線, 而應改用 @private. | 需要指定標志來開啟. |
@protected | @protected 如: /** * Sets the component's root element to the given element. Considered * protected and final. * @param {Element} element Root element for the component. * @protected */ goog.ui.Component.prototype.setElementInternal = function(element) { // ... }; | 指明接下來的方法和屬性是 被保護的. 被保護的方法和屬性的命名不需要以下劃線結尾, 和普通變量名沒區別. | 需要指定標志來開啟. |
@this | @this Type @this {Type} 如: pinto.chat.RosterWidget.extern('getRosterElement', /** * Returns the roster widget element. * @this pinto.chat.RosterWidget * @return {Element} */ function() { return this.getWrappedComponent_().getElement(); }); | 指明調用這個方法時, 需要在哪個上下文中. 當 this 指向的不是原型方法的函數時必須使用這個標記. | 會檢查 |
@supported | @supported 描述 如: /** * @fileoverview Event Manager * Provides an abstracted interface to the * browsers' event systems. * @supported So far tested in IE6 and FF1.5 */ | 在文件概述中用到, 表明支持哪些瀏覽器. | 不需要. |
@enum | @enum {Type} 如: /** * Enum for tri-state values. * @enum {number} */ project.TriState = { TRUE: 1, FALSE: -1, MAYBE: 0 }; | 用于枚舉類型. | 完全支持. 如果省略, 會認為是整型. |
@deprecated | @deprecated 描述 如: /** * Determines whether a node is a field. * @return {boolean} True if the contents of * the element are editable, but the element * itself is not. * @deprecated Use isField(). */ BN_EditUtil.isTopEditableField = function(node) { // ... }; | 告訴其他開發人員, 此方法, 函數已經過時, 不要再使用. 同時也會給出替代方法或函數. | 不需要 |
@override | @override 如: /** * @return {string} Human-readable representation of project.SubClass. * @override */ project.SubClass.prototype.toString() { // ... }; | 指明子類的方法和屬性是故意隱藏了父類的方法和屬性. 如果子類的方法和屬性沒有自己的文檔, 就會繼承父類的. | 會檢查 |
@inheritDoc | @inheritDoc 如: /** @inheritDoc */ project.SubClass.prototype.toString() { // ... }; | 指明子類的方法和屬性是故意隱藏了父類的方法和屬性, 它們具有相同的文檔. 注意: 使用 @inheritDoc 意味著也同時使用了 @override. | 會檢查 |
@code | {@code ...} 如: /** * Moves to the next position in the selection. * Throws {@code goog.iter.StopIteration} when it * passes the end of the range. * @return {Node} The node at the next position. */ goog.dom.RangeIterator.prototype.next = function() { // ... }; | 說明這是一段代碼, 讓它能在生成的文檔中正確的格式化. | 不適用. |
@license or@preserve | @license 描述 如: /** * @preserve Copyright 2009 SomeThirdParty. * Here is the full license text and copyright * notice for this file. Note that the notice can span several * lines and is only terminated by the closing star and slash: */ | 所有被標記為 @license 或 @preserve 的, 會被編譯器保留不做任何修改而直接輸出到最終文擋中. 這個標記讓一些重要的信息(如法律許可或版權信息)原樣保留, 同樣, 文本中的換行也會被保留. | 不需要. |
@noalias | @noalias 如: /** @noalias */ function Range() {} | 在外部文件中使用, 告訴編譯器不要為這個變量或函數重命名. | 不需要. |
@define | @define {Type} 描述 如: /** @define {boolean} */ var TR_FLAGS_ENABLE_DEBUG = true; /** @define {boolean} */ goog.userAgent.ASSUME_IE = false; | 表示該變量可在編譯時被編譯器重新賦值. 在上面例子中, BUILD 文件中指定了 --define='goog.userAgent.ASSUME_IE=true' 這個編譯之后, 常量 goog.userAgent.ASSUME_IE 將被全部直接替換為 true. | 不需要. |
@export | @export 如: /** @export */ foo.MyPublicClass.prototype.myPublicMethod = function() { // ... }; | 上面的例子代碼, 當編譯器運行時指定 --generate_exports 標志, 會生成下面的代碼: goog.exportSymbol('foo.MyPublicClass.prototype.myPublicMethod', foo.MyPublicClass.prototype.myPublicMethod); 編譯后, 將源代碼中的名字原樣導出. 使用 @export 標記時, 應該
| 不需要. |
@const | @const 如: /** @const */ var MY_BEER = 'stout'; /** * My namespace's favorite kind of beer. * @const * @type {string} */ mynamespace.MY_BEER = 'stout'; /** @const */ MyClass.MY_BEER = 'stout'; | 聲明變量為只讀, 直接寫在一行上. 如果其他代碼中重寫該變量值, 編譯器會警告. 常量應全部用大寫字符, 不過使用這個標記, 可以幫你消除命名上依賴. 雖然 jsdoc.org 上列出的 @final 標記作用等價于 @const , 但不建議使用. @const 與 JS1.5 中的 const 關鍵字一致. 注意, 編譯器不禁止修改常量對象的屬性(這與 C++ 中的常量定義不一樣). 如果可以準確推測出常量類型的話, 那么類型申明可以忽略. 如果指定了類型, 應該也寫在同一行上. 變量的額外注釋可寫可不寫. | 支持. |
@nosideeffects | @nosideeffects 如: /** @nosideeffects */ function noSideEffectsFn1() { // ... }; /** @nosideeffects */ var noSideEffectsFn2 = function() { // ... }; /** @nosideeffects */ a.prototype.noSideEffectsFn3 = function() { // ... }; | 用于對函數或構造器聲明, 說明調用此函數不會有副作用. 編譯器遇到此標記時, 如果調用函數的返回值沒有其他地方使用到, 則會將這個函數整個刪除. | 不需要檢查. |
@typedef | @typedef 如: /** @typedef {(string|number)} */ goog.NumberLike; /** @param {goog.NumberLike} x A number or a string. */ goog.readNumber = function(x) { ... } | 這個標記用于給一個復雜的類型取一個別名. | 會檢查 |
@externs | @externs 如: /** * @fileoverview This is an externs file. * @externs */ var document; | 指明一個外部文件. | 不會檢查 |
在第三方代碼中, 你還會見到其他一些 JSDoc 標記. 這些標記在 JSDoc Toolkit Tag Reference 都有介紹到, 但在 Google 的代碼中, 目前不推薦使用. 你可以認為這些是將來會用到的 "保留" 名. 它們包含:
- @augments
- @argument
- @borrows
- @class
- @constant
- @constructs
- @default
- @event
- @example
- @field
- @function
- @ignore
- @inner
- @link
- @memberOf
- @name
- @namespace
- @property
- @public
- @requires
- @returns
- @since
- @static
- @version
JSDoc 中的 HTML
類似于 JavaDoc, JSDoc 支持許多 HTML 標簽, 如 <code>, <pre>, <tt>, <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 <span> tags. */
出來的結果是:
Changes tags to tags.另外, 也應該在源代碼文件中讓其他人更可讀, 所以不要過于使用 HTML 標簽:
/** * Changes <b> tags to <span> tags. */
上面的代碼中, 其他人就很難知道你想干嘛, 直接改成下面的樣子就清楚多了:
/** * Changes 'b' tags to 'span' tags. */
Tips and Tricks
True 和 False 布爾表達式
下面的布爾表達式都返回 false:
null
undefined
''
空字符串0
數字0
但小心下面的, 可都返回 true:
'0'
字符串0[]
空數組{}
空對象
下面段比較糟糕的代碼:
while (x != null) {
你可以直接寫成下面的形式(只要你希望 x 不是 0 和空字符串, 和 false):
while (x) {
如果你想檢查字符串是否為 null 或空:
if (y != null && y != '') {
但這樣會更好:
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 代碼時也是很有用的:
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 = '<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>'; }
你也可以是用數組作為字符串構造器, 然后通過 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); }
Parting Words
保持一致性.
當你在編輯代碼之前, 先花一些時間查看一下現有代碼的風格. 如果他們給算術運算符添加了空格, 你也應該添加. 如果他們的注釋使用一個個星號盒子, 那么也請你使用這種方式.
代碼風格中一個關鍵點是整理一份常用詞匯表, 開發者認同它并且遵循, 這樣在代碼中就能統一表述. 我們在這提出了一些全局上的風格規則, 但也要考慮自身情況形成自己的代碼風格. 但如果你添加的代碼和現有的代碼有很大的區別, 這就讓閱讀者感到很不和諧. 所以, 避免這種情況的發生.
修訂版 2.9
Aaron WhyteBob Jervis
Dan Pupius
Erik Arvidsson
Fritz Schneider
Robby Walker
譯者注: Google JavaScript 編碼風格原文,