Javascript编码规范,好的代码从书写规范开始,增强代码的可读性,可维护性,这是相当重要的!...
1. 前言
JavaScript在百度一直有著廣泛的應(yīng)用,特別是在瀏覽器端的行為管理。本文檔的目標(biāo)是使JavaScript代碼風(fēng)格保持一致,容易被理解和被維護(hù)。
雖然本文檔是針對JavaScript設(shè)計的,但是在使用各種JavaScript的預(yù)編譯語言時(如TypeScript等)時,適用的部分也應(yīng)盡量遵循本文檔的約定。
任何問題或建議,歡迎跟我們討論:fe-styleguide@baidu.com。
2. 代碼風(fēng)格
2.1. 文件
·[建議]?JavaScript?文件使用無?BOM?的?UTF-8?編碼。
解釋
UTF-8 編碼具有更廣泛的適應(yīng)性。BOM 在使用程序或工具處理文件時可能造成不必要的干擾。
·[建議] 在文件結(jié)尾處,保留一個空行。
2.2. 結(jié)構(gòu)
2.2.1. 縮進(jìn)
·[強(qiáng)制] [RULE003] 使用?4?個空格做為一個縮進(jìn)層級,不允許使用?2?個空格 或?tab?字符。
·[強(qiáng)制]?switch?下的?case?和?default?必須增加一個縮進(jìn)層級。
示例
// good
?
switch?(variable) {
?
????case?'1':
????????// do...
????????break;
?
????case?'2':
????????// do...
????????break;
?
????default:
????????// do...
?
}
?
// badswitch?(variable) {
?
case?'1':
????// do...
????break;
?
case?'2':
????// do...
????break;
?
default:
????// do...
?
}
?
2.2.2. 空格
·[強(qiáng)制] [RULE005] 二元運(yùn)算符兩側(cè)必須有一個空格,一元運(yùn)算符與操作對象之間不允許有空格。
示例
var?a = !arr.length;
a++;
a = b + c;
?
·[強(qiáng)制] [RULE006] 用作代碼塊起始的左花括號?{?前必須有一個空格。
示例
// goodif?(condition) {
}
?
while?(condition) {
}
?
function?funcName() {
}
?
// badif?(condition){
}
?
while?(condition){
}
?
function?funcName(){
}
?
·[強(qiáng)制] [RULE007]?if?/?else?/?for?/?while?/?function?/?switch?/?do?/?try?/?catch?/?finally?關(guān)鍵字后,必須有一個空格。
示例
// goodif?(condition) {
}
?
while?(condition) {
}
?
(function?() {
})();
?
// badif(condition) {
}
?
while(condition) {
}
?
(function() {
})();
?
·[強(qiáng)制] [RULE008] 在對象創(chuàng)建時,屬性中的?:?之后必須有空格,:?之前不允許有空格。
示例
// goodvar?obj = {
????a: 1,
????b: 2,
????c: 3
};
?
// badvar?obj = {
????a : 1,
????b:2,
????c :3
};
?
·[強(qiáng)制] [RULE009] 函數(shù)聲明、具名函數(shù)表達(dá)式、函數(shù)調(diào)用中,函數(shù)名和?(?之間不允許有空格。
示例
// goodfunction?funcName() {
}
?
var?funcName = function?funcName() {
};
?
funcName();
?
// badfunction?funcName () {
}
?
var?funcName = function?funcName () {
};
?
funcName ();
?
·[強(qiáng)制]?,?和?;?前不允許有空格。
示例
// good
callFunc(a, b);
?
// bad
callFunc(a , b) ;
?
·[強(qiáng)制] 在函數(shù)調(diào)用、函數(shù)聲明、括號表達(dá)式、屬性訪問、if?/?for?/?while?/?switch?/?catch?等語句中,()?和?[]?內(nèi)緊貼括號部分不允許有空格。
示例
// good
?
callFunc(param1, param2, param3);
?
save(this.list[this.indexes[i]]);
?
needIncream && (variable += increament);
?
if?(num > list.length) {
}
?
while?(len--) {
}
?
?
// bad
?
callFunc( param1, param2, param3 );
?
save( this.list[ this.indexes[ i ] ] );
?
needIncreament && ( variable += increament );
?
if?( num > list.length ) {
}
?
while?( len-- ) {
}
?
·[強(qiáng)制] [RULE012] 單行聲明的數(shù)組與對象,如果包含元素,{}?和?[]?內(nèi)緊貼括號部分不允許包含空格。
解釋
聲明包含元素的數(shù)組與對象,只有當(dāng)內(nèi)部元素的形式較為簡單時,才允許寫在一行。元素復(fù)雜的情況,還是應(yīng)該換行書寫。
示例
// goodvar?arr1 = [];
var?arr2 = [1, 2, 3];
var?obj1 = {};
var?obj2 = {name: 'obj'};
var?obj3 = {
????name: 'obj',
????age: 20,
????sex: 1
};
?
// badvar?arr1 = [ ];
var?arr2 = [ 1, 2, 3 ];
var?obj1 = { };
var?obj2 = { name: 'obj'?};
var?obj3 = {name: 'obj', age: 20, sex: 1};
?
·[強(qiáng)制] [RULE013] 行尾不得有多余的空格。
2.2.3. 換行
·[強(qiáng)制] 每個獨(dú)立語句結(jié)束后必須換行。
·[強(qiáng)制] [RULE015] 每行不得超過?120?個字符。
解釋
超長的不可分割的代碼允許例外,比如復(fù)雜的正則表達(dá)式。長字符串不在例外之列。
·[強(qiáng)制] 運(yùn)算符處換行時,運(yùn)算符必須在新行的行首。
示例
// goodif?(user.isAuthenticated()
????&& user.isInRole('admin')
????&& user.hasAuthority('add-admin')
????|| user.hasAuthority('delete-admin')
) {
????// Code
}
?
var?result = number1 + number2 + number3
????+ number4 + number5;
?
?
// badif?(user.isAuthenticated() &&
????user.isInRole('admin') &&
????user.hasAuthority('add-admin') ||
????user.hasAuthority('delete-admin')) {
????// Code
}
?
var?result = number1 + number2 + number3 +
????number4 + number5;
?
·[強(qiáng)制] [RULE017] 在函數(shù)聲明、函數(shù)表達(dá)式、函數(shù)調(diào)用、對象創(chuàng)建、數(shù)組創(chuàng)建、for語句等場景中,不允許在?,?或?;?前換行。
示例
// goodvar?obj = {
????a: 1,
????b: 2,
????c: 3
};
?
foo(
????aVeryVeryLongArgument,
????anotherVeryLongArgument,
????callback
);
?
?
// badvar?obj = {
????a: 1
????, b: 2
????, c: 3
};
?
foo(
????aVeryVeryLongArgument
????, anotherVeryLongArgument
????, callback
);
?
·[建議] 不同行為或邏輯的語句集,使用空行隔開,更易閱讀。
示例
// 僅為按邏輯換行的示例,不代表setStyle的最優(yōu)實(shí)現(xiàn)function?setStyle(element, property, value) {
????if?(element == null) {
????????return;
????}
?
????element.style[property] = value;
}
?
·[建議] 在語句的行長度超過 120 時,根據(jù)邏輯條件合理縮進(jìn)。
示例
// 較復(fù)雜的邏輯條件組合,將每個條件獨(dú)立一行,邏輯運(yùn)算符放置在行首進(jìn)行分隔,或?qū)⒉糠诌壿嫲催壿嫿M合進(jìn)行分隔。// 建議最終將右括號 ) 與左大括號 { 放在獨(dú)立一行,保證與 if 內(nèi)語句塊能容易視覺辨識。if?(user.isAuthenticated()
????&& user.isInRole('admin')
????&& user.hasAuthority('add-admin')
????|| user.hasAuthority('delete-admin')
) {
????// Code
}
?
// 按一定長度截斷字符串,并使用 + 運(yùn)算符進(jìn)行連接。// 分隔字符串盡量按語義進(jìn)行,如不要在一個完整的名詞中間斷開。// 特別的,對于HTML片段的拼接,通過縮進(jìn),保持和HTML相同的結(jié)構(gòu)。var?html = ''?// 此處用一個空字符串,以便整個HTML片段都在新行嚴(yán)格對齊
????+ '<article>'
????+ ????'<h1>Title here</h1>'
????+ ????'<p>This is a paragraph</p>'
????+ ????'<footer>Complete</footer>'
????+ '</article>';
?
// 也可使用數(shù)組來進(jìn)行拼接,相對 + 更容易調(diào)整縮進(jìn)。var?html = [
????'<article>',
????????'<h1>Title here</h1>',
????????'<p>This is a paragraph</p>',
????????'<footer>Complete</footer>',
????'</article>'
];
html = html.join('');
?
// 當(dāng)參數(shù)過多時,將每個參數(shù)獨(dú)立寫在一行上,并將結(jié)束的右括號 ) 獨(dú)立一行。// 所有參數(shù)必須增加一個縮進(jìn)。
foo(
????aVeryVeryLongArgument,
????anotherVeryLongArgument,
????callback
);
?
// 也可以按邏輯對參數(shù)進(jìn)行組合。// 最經(jīng)典的是baidu.format函數(shù),調(diào)用時將參數(shù)分為“模板”和“數(shù)據(jù)”兩塊
baidu.format(
????dateFormatTemplate,
????year, month, date, hour, minute, second
);
?
// 當(dāng)函數(shù)調(diào)用時,如果有一個或以上參數(shù)跨越多行,應(yīng)當(dāng)每一個參數(shù)獨(dú)立一行。// 這通常出現(xiàn)在匿名函數(shù)或者對象初始化等作為參數(shù)時,如setTimeout函數(shù)等。
setTimeout(
????function?() {
????????alert('hello');
????},
????200
);
?
order.data.read(
????'id='?+ me.model.id,
????function?(data) {
????????me.attchToModel(data.result);
????????callback();
????},
????300
);
?
// 鏈?zhǔn)秸{(diào)用較長時采用縮進(jìn)進(jìn)行調(diào)整。
$('#items')
????.find('.selected')
????.highlight()
????.end();
?
// 三元運(yùn)算符由3部分組成,因此其換行應(yīng)當(dāng)根據(jù)每個部分的長度不同,形成不同的情況。var?result = thisIsAVeryVeryLongCondition
????? resultA : resultB;
?
var?result = condition
????? thisIsAVeryVeryLongResult
????: resultB;
?
// 數(shù)組和對象初始化的混用,嚴(yán)格按照每個對象的 { 和結(jié)束 } 在獨(dú)立一行的風(fēng)格書寫。var?array = [
????{
????????// ...
????},
????{
????????// ...
????}
];
?
·[建議] 對于?if...else...?、?try...catch...finally?等語句,推薦使用在?}?號后添加一個換行 的風(fēng)格,使代碼層次結(jié)構(gòu)更清晰,閱讀性更好。
示例
if?(condition) {
????// some statements;
}
else?{
????// some statements;
}
?
try?{
????// some statements;
}
catch?(ex) {
????// some statements;
}
?
2.2.4. 語句
·[強(qiáng)制] [RULE021] 不得省略語句結(jié)束的分號。
·[強(qiáng)制] [RULE022] 在?if?/?else?/?for?/?do?/?while?語句中,即使只有一行,也不得省略塊?{...}。
示例
// goodif?(condition) {
????callFunc();
}
?
// badif?(condition) callFunc();
if?(condition)
????callFunc();
?
·[強(qiáng)制] [RULE023] 函數(shù)定義結(jié)束不允許添加分號。
示例
// goodfunction?funcName() {
}
?
// badfunction?funcName() {
};
?
// 如果是函數(shù)表達(dá)式,分號是不允許省略的。var?funcName = function?() {
};
?
·[強(qiáng)制] [RULE024]?IIFE?必須在函數(shù)表達(dá)式外添加?(,非?IIFE?不得在函數(shù)表達(dá)式外添加?(?。
解釋
額外的 ( 能夠讓代碼在閱讀的一開始就能判斷函數(shù)是否立即被調(diào)用,進(jìn)而明白接下來代碼的用途。而不是一直拖到底部才恍然大悟。
示例
// goodvar?task = (function?() {
???// Code
???return?result;
})();
?
var?func = function?() {
};
?
?
// badvar?task = function?() {
????// Code
????return?result;
}();
?
var?func = (function?() {
});
?
2.3. 命名
·[強(qiáng)制]?變量?使用?Camel命名法?。
示例
var?loadingModules = {};
?
·[強(qiáng)制]?常量?使用?全部字母大寫,單詞間下劃線分隔?的命名方式。
示例
var?HTML_ENTITY = {};
?
·[強(qiáng)制] 函數(shù) 使用?Camel命名法?。
示例
function?stringFormat(source) {
}
?
·[強(qiáng)制] 函數(shù)的?參數(shù)?使用?Camel命名法?。
示例
function?hear(theBells) {
}
?
·[強(qiáng)制] [RULE029]?類?使用?Pascal命名法?。
示例
function?TextNode(options) {
}
?
·[強(qiáng)制] [RULE025] 類的?方法?/?屬性?使用?Camel命名法?。
示例
function?TextNode(value, engine) {
????this.value = value;
????this.engine = engine;
}
?
TextNode.prototype.clone = function?() {
????return?this;
};
?
·[強(qiáng)制]?枚舉變量?使用?Pascal命名法?,枚舉的屬性?使用?全部字母大寫,單詞間下劃線分隔?的命名方式。
示例
var?TargetState = {
????READING: 1,
????READED: 2,
????APPLIED: 3,
????READY: 4
};
?
·[強(qiáng)制] 命名空間 使用?Camel命名法?。
示例
?
equipments.heavyWeapons = {};
?
·[強(qiáng)制] 由多個單詞組成的縮寫詞,在命名中,根據(jù)當(dāng)前命名法和出現(xiàn)的位置,所有字母的大小寫與首字母的大小寫保持一致。
示例
function?XMLParser() {
}
?
function?insertHTML(element, html) {
}
?
var?httpRequest = new?HTTPRequest();
?
·[強(qiáng)制]?類名?使用?名詞?。
示例
function?Engine(options) {
}
?
·[建議]?函數(shù)名?使用?動賓短語?。
示例
function?getStyle(element) {
}
?
·[建議]?boolean?類型的變量使用?is?或?has?開頭。
示例
var?isReady = false;
var?hasMoreCommands = false;
?
·[建議]?Promise對象?用?動賓短語的進(jìn)行時?表達(dá)。
示例
var?loadingData = ajax.get('url');
loadingData.then(callback);
?
2.4. 注釋
2.4.1. 單行注釋
·[強(qiáng)制] [RULE038] 必須獨(dú)占一行。//?后跟一個空格,縮進(jìn)與下一行被注釋說明的代碼一致。
2.4.2. 多行注釋
·[建議] 避免使用?/*...*/?這樣的多行注釋。有多行注釋內(nèi)容時,使用多個單行注釋。
2.4.3. 文檔化注釋
·[強(qiáng)制] 為了便于代碼閱讀和自文檔化,以下內(nèi)容必須包含以?/**...*/?形式的塊注釋中。
解釋
·[強(qiáng)制] 文檔注釋前必須空一行。
·[建議] 自文檔化的文檔說明 what,而不是 how。
2.4.4. 類型定義
·[強(qiáng)制] 類型定義都是以?{?開始, 以?}?結(jié)束。
解釋
常用類型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。
類型不僅局限于內(nèi)置的類型,也可以是自定義的類型。比如定義了一個類 Developer,就可以使用它來定義一個參數(shù)和返回值的類型。
·[強(qiáng)制] 對于基本類型 {string}, {number}, {boolean},首字母必須小寫。
| 類型定義 | 語法示例 | 解釋 |
| String | {string} | – |
| String | {string} | – |
| Boolean | {boolean} | – |
| Object | {Object} | – |
| Function | {Function} | – |
| RegExp | {RegExp} | – |
| Array | {Array} | – |
| Date | {Date} | – |
| 單一類型集合 | {Array.<string>} | string 類型的數(shù)組 |
| 多類型 | {(number|boolean)} | 可能是 number 類型, 也可能是 boolean 類型 |
| 允許為null | {?number} | 可能是 number, 也可能是 null |
| 不允許為null | {!Object} | Object 類型, 但不是 null |
| Function類型 | {function(number, boolean)} | 函數(shù), 形參類型 |
| Function帶返回值 | {function(number, boolean):string} | 函數(shù), 形參, 返回值類型 |
| 參數(shù)可選 | @param {string=} name | 可選參數(shù), =為類型后綴 |
| 可變參數(shù) | @param {...number} args | 變長參數(shù), ...為類型前綴 |
| 任意類型 | {*} | 任意類型 |
| 可選任意類型 | @param {*=} name | 可選參數(shù),類型不限 |
| 可變?nèi)我忸愋?/p> | @param {...*} args | 變長參數(shù),類型不限 |
2.4.5. 文件注釋
·[強(qiáng)制] 文件頂部必須包含文件注釋,用?@file?標(biāo)識文件說明。
示例
/** * @file Describe the file */
?
·[建議] 文件注釋中可以用?@author?標(biāo)識開發(fā)者信息。
解釋
開發(fā)者信息能夠體現(xiàn)開發(fā)人員對文件的貢獻(xiàn),并且能夠讓遇到問題或希望了解相關(guān)信息的人找到維護(hù)人。通常情況文件在被創(chuàng)建時標(biāo)識的是創(chuàng)建者。
隨著項(xiàng)目的進(jìn)展,越來越多的人加入,參與這個文件的開發(fā),新的作者應(yīng)該被加入?@author?標(biāo)識。
@author?標(biāo)識具有多人時,原則是按照?責(zé)任?進(jìn)行排序。通常的說就是如果有問題,就是找第一個人應(yīng)該比找第二個人有效。
比如文件的創(chuàng)建者由于各種原因,模塊移交給了其他人或其他團(tuán)隊(duì),后來因?yàn)樾略鲂枨?#xff0c;其他人在新增代碼時,添加?@author?標(biāo)識應(yīng)該把自己的名字添加在創(chuàng)建人的前面。
@author?中的名字不允許被刪除。任何勞動成果都應(yīng)該被尊重。
業(yè)務(wù)項(xiàng)目中,一個文件可能被多人頻繁修改,并且每個人的維護(hù)時間都可能不會很長,不建議為文件增加?@author?標(biāo)識。通過版本控制系統(tǒng)追蹤變更,按業(yè)務(wù)邏輯單元確定模塊的維護(hù)責(zé)任人,通過文檔與wiki跟蹤和查詢,是更好的責(zé)任管理方式。
對于業(yè)務(wù)邏輯無關(guān)的技術(shù)型基礎(chǔ)項(xiàng)目,特別是開源的公共項(xiàng)目,應(yīng)使用?@author?標(biāo)識。
示例
/** * @file Describe the file * @author author-name(mail-name@domain.com) * ????????author-name2(mail-name2@domain.com) */
?
2.4.6. 命名空間注釋
·[建議] 命名空間使用?@namespace?標(biāo)識。
示例
/** * @namespace */var?util = {};
?
2.4.7. 類注釋
·[建議] 使用?@class?標(biāo)記類或構(gòu)造函數(shù)。
解釋
對于使用對象?constructor?屬性來定義的構(gòu)造函數(shù),可以使用?@constructor?來標(biāo)記。
示例
/** * 描述 * * @class */function?Developer() {
????// constructor body
}
?
·[建議] 使用?@extends?標(biāo)記類的繼承信息。
示例
/** * 描述 * * @class * @extends Developer */function?Fronteer() {
????Developer.call(this);
????// constructor body
}
util.inherits(Fronteer, Developer);
?
·[強(qiáng)制] 使用包裝方式擴(kuò)展類成員時, 必須通過?@lends?進(jìn)行重新指向。
解釋
沒有?@lends?標(biāo)記將無法為該類生成包含擴(kuò)展類成員的文檔。
示例
/** * 類描述 * * @class * @extends Developer */function?Fronteer() {
????Developer.call(this);
????// constructor body
}
?
util.extend(
????Fronteer.prototype,
????/** @lends Fronteer.prototype */{
????????_getLevel: function?() {
????????????// TODO
????????}
????}
);
?
·[強(qiáng)制] 類的屬性或方法等成員信息使用?@public?/?@protected?/?@private?中的任意一個,指明可訪問性。
解釋
生成的文檔中將有可訪問性的標(biāo)記,避免用戶直接使用非?public?的屬性或方法。
示例
/** * 類描述 * * @class * @extends Developer */var?Fronteer = function?() {
????Developer.call(this);
?
????/** ????* 屬性描述 ????* ????* @type {string} ????* @private ????*/
????this._level = 'T12';
?
????// constructor body
};
util.inherits(Fronteer, Developer);
?
/** * 方法描述 * * @private * @return {string} 返回值描述 */
Fronteer.prototype._getLevel = function?() {
};
?
2.4.8. 函數(shù)/方法注釋
·[強(qiáng)制] 函數(shù)/方法注釋必須包含函數(shù)說明,有參數(shù)和返回值時必須使用注釋標(biāo)識。
·[強(qiáng)制] 參數(shù)和返回值注釋必須包含類型信息和說明。
·[建議] 當(dāng)函數(shù)是內(nèi)部函數(shù),外部不可訪問時,可以使用?@inner?標(biāo)識。
示例
/** * 函數(shù)描述 * * @param {string} p1 參數(shù)1的說明 * @param {string} p2 參數(shù)2的說明,比較長 * ????那就換行了. * @param {number=} p3 參數(shù)3的說明(可選) * @return {Object} 返回值描述 */function?foo(p1, p2, p3) {
????var?p3 = p3 || 10;
????return?{
????????p1: p1,
????????p2: p2,
????????p3: p3
????};
}
?
·[強(qiáng)制] 對 Object 中各項(xiàng)的描述, 必須使用?@param?標(biāo)識。
示例
/** * 函數(shù)描述 * * @param {Object} option 參數(shù)描述 * @param {string} option.url option項(xiàng)描述 * @param {string=} option.method option項(xiàng)描述,可選參數(shù) */function?foo(option) {
????// TODO
}
?
·[建議] 重寫父類方法時, 應(yīng)當(dāng)添加?@override?標(biāo)識。如果重寫的形參個數(shù)、類型、順序和返回值類型均未發(fā)生變化,可省略?@param?、@return?,僅用 @override 標(biāo)識,否則仍應(yīng)作完整注釋。
解釋
簡而言之,當(dāng)子類重寫的方法能直接套用父類的方法注釋時可省略對參數(shù)與返回值的注釋。
2.4.9. 事件注釋
·[強(qiáng)制] 必須使用 @event 標(biāo)識事件,事件參數(shù)的標(biāo)識與方法描述的參數(shù)標(biāo)識相同。
示例
/** * 值變更時觸發(fā) * * @event * @param {Object} e e描述 * @param {string} e.before before描述 * @param {string} e.after after描述 */
onchange: function?(e) {
}
?
·[強(qiáng)制] 在會廣播事件的函數(shù)前使用?@fires?標(biāo)識廣播的事件,在廣播事件代碼前使用?@event?標(biāo)識事件。
·[建議] 對于事件對象的注釋,使用?@param?標(biāo)識,生成文檔時可讀性更好。
示例
/** * 點(diǎn)擊處理 * * @fires Select#change * @private */
Select.prototype.clickHandler = function?() {
????/** ????* 值變更時觸發(fā) ????* ????* @event Select#change ????* @param {Object} e e描述 ????* @param {string} e.before before描述 ????* @param {string} e.after after描述 ????*/
????this.fire(
????????'change',
????????{
????????????before: 'foo',
????????????after: 'bar'
????????}
????);
};
?
2.4.10. 常量注釋
·[強(qiáng)制] 常量必須使用?@const?標(biāo)記,并包含說明和類型信息。
示例
/** * 常量說明 * * @const * @type {string} */var?REQUEST_URL = 'myurl.do';
?
2.4.11. 復(fù)雜類型注釋
·[建議] 對于類型未定義的復(fù)雜結(jié)構(gòu)的注釋,可以使用?@typedef?標(biāo)識來定義。
示例
// `namespaceA~` 可以換成其它 namepaths 前綴,目的是為了生成文檔中能顯示 `@typedef` 定義的類型和鏈接。/** * 服務(wù)器 * * @typedef {Object} namespaceA~Server * @property {string} host 主機(jī) * @property {number} port 端口 */
?
/** * 服務(wù)器列表 * * @type {Array.<namespaceA~Server>} */var?servers = [
????{
????????host: '1.2.3.4',
????????port: 8080
????},
????{
????????host: '1.2.3.5',
????????port: 8081
????}
];
?
2.4.12. AMD 模塊注釋
·[強(qiáng)制] AMD 模塊使用?@module?或?@exports?標(biāo)識。
解釋
@exports?與?@module?都可以用來標(biāo)識模塊,區(qū)別在于?@module?可以省略模塊名稱。而只使用?@exports?時在 namepaths 中可以省略 module: 前綴。
示例
?
define(
????function?(require) {
?
????????/** ????????* foo description ????????* ????????* @exports Foo ????????*/
????????var?foo = {
????????????// TODO
????????};
?
????????/** ????????* baz description ????????* ????????* @return {boolean} return description ????????*/
????????foo.baz = function?() {
????????????// TODO
????????};
?
????????return?foo;
?
????}
);
?
也可以在 exports 變量前使用?@module?標(biāo)識:
?
define(
????function?(require) {
?
????????/** ????????* module description. ????????* ????????* @module foo ????????*/
????????var?exports = {};
?
?
????????/** ????????* bar description ????????* ????????*/
????????exports.bar = function?() {
????????????// TODO
????????};
?
????????return?exports;
????}
);
?
如果直接使用 factory 的 exports 參數(shù),還可以:
/** * module description. * * @module */
define(
????function?(require, exports) {
?
????????/** ????????* bar description ????????* ????????*/
????????exports.bar = function?() {
????????????// TODO
????????};
????????return?exports;
????}
);
?
·[強(qiáng)制] 對于已使用?@module?標(biāo)識為 AMD模塊 的引用,在?namepaths?中必須增加?module:?作前綴。
解釋
namepaths?沒有?module:?前綴時,生成的文檔中將無法正確生成鏈接。
示例
/** * 點(diǎn)擊處理 * * @fires module:Select#change * @private */
Select.prototype.clickHandler = function?() {
????/** ????* 值變更時觸發(fā) ????* ????* @event module:Select#change ????* @param {Object} e e描述 ????* @param {string} e.before before描述 ????* @param {string} e.after after描述 ????*/
????this.fire(
????????'change',
????????{
????????????before: 'foo',
????????????after: 'bar'
????????}
????);
};
?
·[建議] 對于類定義的模塊,可以使用?@alias?標(biāo)識構(gòu)建函數(shù)。
示例
/** * A module representing a jacket. * @module jacket */
define(
????function?() {
?
????????/** ????????* @class ????????* @alias module:jacket ????????*/
????????var?Jacket = function?() {
????????};
?
????????return?Jacket;
????}
);
?
·[建議] 多模塊定義時,可以使用?@exports?標(biāo)識各個模塊。
示例
// one module
define('html/utils',
????/** ????* Utility functions to ease working with DOM elements. ????* @exports html/utils ????*/
????function?() {
????????var?exports = {
????????};
?
????????return?exports;
????}
);
?
// another module
define('tag',
????/** @exports tag */
????function?() {
????????var?exports = {
????????};
?
????????return?exports;
????}
);
?
·[建議] 對于 exports 為 Object 的模塊,可以使用?@namespace?標(biāo)識。
解釋
使用?@namespace?而不是?@module?或?@exports?時,對模塊的引用可以省略 module: 前綴。
·[建議] 對于 exports 為類名的模塊,使用?@class?和?@exports?標(biāo)識。
示例
// 只使用 @class Bar 時,類方法和屬性都必須增加 @name Bar#methodName 來標(biāo)識,與 @exports 配合可以免除這一麻煩,并且在引用時可以省去 module: 前綴。// 另外需要注意類名需要使用 var 定義的方式。
?
/** * Bar description * * @see foo * @exports ?Bar * @class */var?Bar = function?() {
????// TODO
};
?
/** * baz description * * @return {(string|Array)} return description */
Bar.prototype.baz = function?() {
????// TODO
};
?
2.4.13. 細(xì)節(jié)注釋
對于內(nèi)部實(shí)現(xiàn)、不容易理解的邏輯說明、摘要信息等,我們可能需要編寫細(xì)節(jié)注釋。
·[建議] 細(xì)節(jié)注釋遵循單行注釋的格式。說明必須換行時,每行是一個單行注釋的起始。
示例
function?foo(p1, p2) {
????// 這里對具體內(nèi)部邏輯進(jìn)行說明
????// 說明太長需要換行
????for?(...) {
????????....
????}
}
?
·[強(qiáng)制] 有時我們會使用一些特殊標(biāo)記進(jìn)行說明。特殊標(biāo)記必須使用單行注釋的形式。下面列舉了一些常用標(biāo)記:
解釋
- TODO: 有功能待實(shí)現(xiàn)。此時需要對將要實(shí)現(xiàn)的功能進(jìn)行簡單說明。
- FIXME: 該處代碼運(yùn)行沒問題,但可能由于時間趕或者其他原因,需要修正。此時需要對如何修正進(jìn)行簡單說明。
- HACK: 為修正某些問題而寫的不太好或者使用了某些詭異手段的代碼。此時需要對思路或詭異手段進(jìn)行描述。
- XXX: 該處存在陷阱。此時需要對陷阱進(jìn)行描述。
3. 語言特性
3.1. 變量
·[強(qiáng)制] [RULE070] 變量在使用前必須通過?var?定義。
解釋
不通過?var?定義變量將導(dǎo)致變量污染全局環(huán)境。
示例
// goodvar?name = 'MyName';
?
// bad
name = 'MyName';
?
·[強(qiáng)制] [RULE071] 每個?var?只能聲明一個變量。
解釋
一個?var?聲明多個變量,容易導(dǎo)致較長的行長度,并且在修改時容易造成逗號和分號的混淆。
示例
// goodvar?hangModules = [];
var?missModules = [];
var?visited = {};
?
// badvar?hangModules = [],
????missModules = [],
????visited = {};
?
·[強(qiáng)制] [RULE072] 變量必須?即用即聲明?,不得在函數(shù)或其它形式的代碼塊起始位置統(tǒng)一聲明所有變量。
解釋
變量聲明與使用的距離越遠(yuǎn),出現(xiàn)的跨度越大,代碼的閱讀與維護(hù)成本越高。雖然JavaScript的變量是函數(shù)作用域,還是應(yīng)該根據(jù)編程中的意圖,縮小變量出現(xiàn)的距離空間。
示例
// goodfunction?kv2List(source) {
????var?list = [];
?
????for?(var?key in?source) {
????????if?(source.hasOwnProperty(key)) {
????????????var?item = {
????????????????k: key,
????????????????v: source[key]
????????????};
????????????list.push(item);
????????}
????}
?
????return?list;
}
?
// badfunction?kv2List(source) {
????var?list = [];
????var?key;
????var?item;
?
????for?(key in?source) {
????????if?(source.hasOwnProperty(key)) {
????????????item = {
????????????????k: key,
????????????????v: source[key]
????????????};
????????????list.push(item);
????????}
????}
?
????return?list;
}
?
3.2. 條件
·[強(qiáng)制] [RULE073] 在 Equality Expression 中使用類型嚴(yán)格的?===?。僅當(dāng)判斷?null?或?undefined?時,允許使用?==?null?。
解釋
使用?===?可以避免等于判斷中隱式的類型轉(zhuǎn)換。
示例
// goodif?(age === 30) {
????// ......
}
?
// badif?(age == 30) {
????// ......
}
?
·[建議] 盡可能使用簡潔的表達(dá)式。
示例
// 字符串為空
?
// goodif?(!name) {
????// ......
}
?
// badif?(name === '') {
????// ......
}
?
// 字符串非空
?
// goodif?(name) {
????// ......
}
?
// badif?(name !== '') {
????// ......
}
?
// 數(shù)組非空
?
// goodif?(collection.length) {
????// ......
}
?
// badif?(collection.length > 0) {
????// ......
}
?
// 布爾不成立
?
// goodif?(!notTrue) {
????// ......
}
?
// badif?(notTrue === false) {
????// ......
}
?
// null 或 undefined
?
// goodif?(noValue == null) {
??// ......
}
?
// badif?(noValue === null?|| typeof?noValue === 'undefined') {
??// ......
}
?
·[建議] 按執(zhí)行頻率排列分支的順序。
解釋
按執(zhí)行頻率排列分支的順序好處是:
- 閱讀的人容易找到最常見的情況,增加可讀性。
- 提高執(zhí)行效率。
·[建議] 對于相同變量或表達(dá)式的多值條件,用?switch?代替?if?。
示例
// goodswitch?(typeof?variable) {
????case?'object':
????????// ......
????????break;
????case?'number':
????case?'boolean':
????case?'string':
????????// ......
????????break;
}
?
// badvar?type = typeof?variable;
if?(type === 'object') {
????// ......
}
else?if?(type === 'number'?|| type === 'boolean'?|| type === 'string') {
????// ......
}
?
·[建議] 如果函數(shù)或全局中的?else?塊后沒有任何語句,可以刪除?else?。
示例
// goodfunction?getName() {
????if?(name) {
????????return?name;
????}
?
????return?'unnamed';
}
?
// badfunction?getName() {
????if?(name) {
????????return?name;
????}
????else?{
????????return?'unnamed';
????}
}
?
3.3. 循環(huán)
·[建議] 不要在循環(huán)體中包含函數(shù)表達(dá)式,事先將函數(shù)提取到循環(huán)體外。
解釋
循環(huán)體中的函數(shù)表達(dá)式,運(yùn)行過程中會生成循環(huán)次數(shù)個函數(shù)對象。
示例
// goodfunction?clicker() {
????// ......
}
?
for?(var?i = 0, len = elements.length; i < len; i++) {
????var?element = elements[i];
????addListener(element, 'click', clicker);
}
?
?
// badfor?(var?i = 0, len = elements.length; i < len; i++) {
????var?element = elements[i];
????addListener(element, 'click', function?() {});
}
?
·[建議] 對循環(huán)內(nèi)多次使用的不變值,在循環(huán)外用變量緩存。
示例
// goodvar?width = wrap.offsetWidth + 'px';
for?(var?i = 0, len = elements.length; i < len; i++) {
????var?element = elements[i];
????element.style.width = width;
????// ......
}
?
?
// badfor?(var?i = 0, len = elements.length; i < len; i++) {
????var?element = elements[i];
????element.style.width = wrap.offsetWidth + 'px';
????// ......
}
?
·[建議] 對有序集合進(jìn)行遍歷時,緩存?length?。
解釋
雖然現(xiàn)代瀏覽器都對數(shù)組長度進(jìn)行了緩存,但對于一些宿主對象和老舊瀏覽器的數(shù)組對象,在每次?length?訪問時會動態(tài)計算元素個數(shù),此時緩存?length?能有效提高程序性能。
示例
for?(var?i = 0, len = elements.length; i < len; i++) {
????var?element = elements[i];
????// ......
}
?
·[建議] 對有序集合進(jìn)行順序無關(guān)的遍歷時,使用逆序遍歷。
解釋
逆序遍歷可以節(jié)省變量,代碼比較優(yōu)化。
示例
var?len = elements.length;
while?(len--) {
????var?element = elements[len];
????// ......
}
?
3.4. 類型
3.4.1. 類型檢測
·[建議] 類型檢測優(yōu)先使用?typeof?。對象類型檢測使用?instanceof?。null?或?undefined?的檢測使用?==?null?。
示例
// stringtypeof?variable === 'string'
?
// numbertypeof?variable === 'number'
?
// booleantypeof?variable === 'boolean'
?
// Functiontypeof?variable === 'function'
?
// Objecttypeof?variable === 'object'
?
// RegExp
variable instanceof?RegExp
?
// Array
variable instanceof?Array
?
// null
variable === null
?
// null or undefined
variable == null
?
// undefinedtypeof?variable === 'undefined'
?
3.4.2. 類型轉(zhuǎn)換
·[建議] 轉(zhuǎn)換成?string?時,使用?+?''?。
示例
// good
num + '';
?
// badnew?String(num);
num.toString();
String(num);
?
·[建議] 轉(zhuǎn)換成?number?時,通常使用?+?。
示例
// good
+str;
?
// bad
Number(str);
?
·[建議]?string?轉(zhuǎn)換成?number?,要轉(zhuǎn)換的字符串結(jié)尾包含非數(shù)字并期望忽略時,使用?parseInt。
示例
var?width = '200px';
parseInt(width, 10);
?
·[強(qiáng)制] [RULE086] 使用?parseInt?時,必須指定進(jìn)制。
示例
// good
parseInt(str, 10);
?
// bad
parseInt(str);
?
·[建議] 轉(zhuǎn)換成?boolean?時,使用?!!?。
示例
var?num = 3.14;
!!num;
?
·[建議]?number?去除小數(shù)點(diǎn),使用?Math.floor?/?Math.round?/?Math.ceil?,不使用?parseInt。
示例
// goodvar?num = 3.14;
Math.ceil(num);
?
// badvar?num = 3.14;
parseInt(num, 10);
?
3.5. 字符串
·[強(qiáng)制] [RULE089] 字符串開頭和結(jié)束使用單引號?'?。
解釋
- 輸入單引號不需要按住 shift,方便輸入。
- 實(shí)際使用中,字符串經(jīng)常用來拼接 HTML。為方便 HTML 中包含雙引號而不需要轉(zhuǎn)義寫法。
示例
var?str = '我是一個字符串';
var?html = '<div class="cls">拼接HTML可以省去雙引號轉(zhuǎn)義</div>';
?
·[建議] 使用?數(shù)組?或?+?拼接字符串。
解釋
- 使用 + 拼接字符串,如果拼接的全部是 StringLiteral,壓縮工具可以對其進(jìn)行自動合并的優(yōu)化。所以,靜態(tài)字符串建議使用 + 拼接。
- 在現(xiàn)代瀏覽器下,使用 + 拼接字符串,性能較數(shù)組的方式要高。
- 如需要兼顧老舊瀏覽器,應(yīng)盡量使用數(shù)組拼接字符串。
示例
// 使用數(shù)組拼接字符串var?str = [
????// 推薦換行開始并縮進(jìn)開始第一個字符串, 對齊代碼, 方便閱讀.
????'<ul>',
????????'<li>第一項(xiàng)</li>',
????????'<li>第二項(xiàng)</li>',
????'</ul>'
].join('');
?
// 使用 + 拼接字符串var?str2 = ''?// 建議第一個為空字符串, 第二個換行開始并縮進(jìn)開始, 對齊代碼, 方便閱讀
????+ '<ul>',
????+ ???'<li>第一項(xiàng)</li>',
????+ ???'<li>第二項(xiàng)</li>',
????+ '</ul>';
?
·[建議] 復(fù)雜的數(shù)據(jù)到視圖字符串的轉(zhuǎn)換過程,選用一種模板引擎。
解釋
使用模板引擎有如下好處:
- artTemplate: 體積較小,在所有環(huán)境下性能高,語法靈活。
- dot.js: 體積小,在現(xiàn)代瀏覽器下性能高,語法靈活。
- etpl: 體積較小,在所有環(huán)境下性能高,模板復(fù)用性高,語法靈活。
- handlebars: 體積大,在所有環(huán)境下性能高,擴(kuò)展性高。
- hogon: 體積小,在現(xiàn)代瀏覽器下性能高。
- nunjucks: 體積較大,性能一般,模板復(fù)用性高。
3.6. 對象
·[強(qiáng)制] [RULE092] 使用對象字面量?{}?創(chuàng)建新?Object?。
示例
// goodvar?obj = {};
?
// badvar?obj = new?Object();
?
·[強(qiáng)制] [RULE094] 對象創(chuàng)建時,如果一個對象的所有?屬性?均可以不添加引號,則所有?屬性?不得添加引號。
示例
var?info = {
????name: 'someone',
????age: 28
};
?
·[強(qiáng)制] 對象創(chuàng)建時,如果任何一個?屬性?需要添加引號,則所有?屬性?必須添加?'?。
解釋
如果屬性不符合 Identifier 和 NumberLiteral 的形式,就需要以 StringLiteral 的形式提供。
示例
// goodvar?info = {
????'name': 'someone',
????'age': 28,
????'more-info': '...'
};
?
// badvar?info = {
????name: 'someone',
????age: 28,
????'more-info': '...'
};
?
·[強(qiáng)制] [RULE095] 不允許修改和擴(kuò)展任何原生對象和宿主對象的原型。
示例
// 以下行為絕對禁止
String.prototype.trim = function?() {
};
?
·[建議] 屬性訪問時,盡量使用?.?。
解釋
屬性名符合 Identifier 的要求,就可以通過?.?來訪問,否則就只能通過?[expr]?方式訪問。
通常在 JavaScript 中聲明的對象,屬性命名是使用 Camel 命名法,用?.?來訪問更清晰簡潔。部分特殊的屬性(比如來自后端的JSON),可能采用不尋常的命名方式,可以通過?[expr]?方式訪問。
示例
?
info.age;
info['more-info'];
?
·[建議]?for?in?遍歷對象時, 使用?hasOwnProperty?過濾掉原型中的屬性。
示例
var?newInfo = {};
for?(var?key in?info) {
????if?(info.hasOwnProperty(key)) {
????????newInfo[key] = info[key];
????}
}
?
3.7. 數(shù)組
·[強(qiáng)制] [RULE098] 使用數(shù)組字面量?[]?創(chuàng)建新數(shù)組,除非想要創(chuàng)建的是指定長度的數(shù)組。
示例
// goodvar?arr = [];
?
// badvar?arr = new?Array();
?
·[強(qiáng)制] 遍歷數(shù)組不使用?for?in。
解釋
數(shù)組對象可能存在數(shù)字以外的屬性, 這種情況下?for?in?不會得到正確結(jié)果.
示例
var?arr = ['a', 'b', 'c'];
arr.other = 'other things'; // 這里僅作演示, 實(shí)際中應(yīng)使用Object類型
?
// 正確的遍歷方式for?(var?i = 0, len = arr.length; i < len; i++) {
????console.log(i);
}
?
// 錯誤的遍歷方式for?(i in?arr) {
????console.log(i);
}
?
·[建議] 不因?yàn)樾阅艿脑蜃约簩?shí)現(xiàn)數(shù)組排序功能,盡量使用數(shù)組的?sort?方法。
解釋
自己實(shí)現(xiàn)的常規(guī)排序算法,在性能上并不優(yōu)于數(shù)組默認(rèn)的 sort 方法。以下兩種場景可以自己實(shí)現(xiàn)排序:
- 需要穩(wěn)定的排序算法,達(dá)到嚴(yán)格一致的排序結(jié)果。
- 數(shù)據(jù)特點(diǎn)鮮明,適合使用桶排。
·[建議] 清空數(shù)組使用?.length?=?0?。
3.8. 函數(shù)
3.8.1. 函數(shù)長度
·[建議] 一個函數(shù)的長度控制在?50?行以內(nèi)。
解釋
將過多的邏輯單元混在一個大函數(shù)中,易導(dǎo)致難以維護(hù)。一個清晰易懂的函數(shù)應(yīng)該完成單一的邏輯單元。復(fù)雜的操作應(yīng)進(jìn)一步抽取,通過函數(shù)的調(diào)用來體現(xiàn)流程。
特定算法等不可分割的邏輯允許例外。
示例
function?syncViewStateOnUserAction() {
????if?(x.checked) {
????????y.checked = true;
????????z.value = '';
????}
????else?{
????????y.checked = false;
????}
?
????if?(!a.value) {
????????warning.innerText = 'Please enter it';
????????submitButton.disabled = true;
????}
????else?{
????????warning.innerText = '';
????????submitButton.disabled = false;
????}
}
?
// 直接閱讀該函數(shù)會難以明確其主線邏輯,因此下方是一種更合理的表達(dá)方式:
?
function?syncViewStateOnUserAction() {
????syncXStateToView();
????checkAAvailability();
}
?
function?syncXStateToView() {
????if?(x.checked) {
????????y.checked = true;
????????z.value = '';
????}
????else?{
????????y.checked = false;
????}
}
?
function?checkAAvailability() {
????if?(!a.value) {
????????displayWarningForAMissing();
????}
????else?{
????????clearWarnignForA();
????}
}
?
3.8.2. 參數(shù)設(shè)計
·[建議] 一個函數(shù)的參數(shù)控制在?6?個以內(nèi)。
解釋
除去不定長參數(shù)以外,函數(shù)具備不同邏輯意義的參數(shù)建議控制在 6 個以內(nèi),過多參數(shù)會導(dǎo)致維護(hù)難度增大。
某些情況下,如使用 AMD Loader 的 require 加載多個模塊時,其 callback 可能會存在較多參數(shù),因此對函數(shù)參數(shù)的個數(shù)不做強(qiáng)制限制。
·[建議] 通過?options?參數(shù)傳遞非數(shù)據(jù)輸入型參數(shù)。
解釋
有些函數(shù)的參數(shù)并不是作為算法的輸入,而是對算法的某些分支條件判斷之用,此類參數(shù)建議通過一個 options 參數(shù)傳遞。
如下函數(shù):
/** * 移除某個元素 * * @param {Node} element 需要移除的元素 * @param {boolean} removeEventListeners 是否同時將所有注冊在元素上的事件移除 */function?removeElement(element, removeEventListeners) {
????element.parent.removeChild(element);
????if?(removeEventListeners) {
????????element.clearEventListeners();
????}
}
?
可以轉(zhuǎn)換為下面的簽名:
/** * 移除某個元素 * * @param {Node} element 需要移除的元素 * @param {Object} options 相關(guān)的邏輯配置 * @param {boolean} options.removeEventListeners 是否同時將所有注冊在元素上的事件移除 */function?removeElement(element, options) {
????element.parent.removeChild(element);
????if?(options.removeEventListeners) {
????????element.clearEventListeners();
????}
}
?
這種模式有幾個顯著的優(yōu)勢:
- boolean 型的配置項(xiàng)具備名稱,從調(diào)用的代碼上更易理解其表達(dá)的邏輯意義。
- 當(dāng)配置項(xiàng)有增長時,無需無休止地增加參數(shù)個數(shù),不會出現(xiàn) removeElement(element, true, false, false, 3) 這樣難以理解的調(diào)用代碼。
- 當(dāng)部分配置參數(shù)可選時,多個參數(shù)的形式非常難處理重載邏輯,而使用一個 options 對象只需判斷屬性是否存在,實(shí)現(xiàn)得以簡化。
3.8.3. 閉包
·[建議] 在適當(dāng)?shù)臅r候?qū)㈤]包內(nèi)大對象置為?null?。
解釋
在 JavaScript 中,無需特別的關(guān)鍵詞就可以使用閉包,一個函數(shù)可以任意訪問在其定義的作用域外的變量。
需要注意的是,函數(shù)的作用域是靜態(tài)的,即在定義時決定,與調(diào)用的時機(jī)和方式?jīng)]有任何關(guān)系。
閉包會阻止一些變量的垃圾回收,對于較老舊的JavaScript引擎,可能導(dǎo)致外部所有變量均無法回收。
首先一個較為明確的結(jié)論是,以下內(nèi)容會影響到閉包內(nèi)變量的回收:
- 嵌套的函數(shù)中是否有使用該變量。
- 嵌套的函數(shù)中是否有直接調(diào)用eval?。
- 是否使用了 with 表達(dá)式。
Chakra、V8 和 SpiderMonkey 將受以上因素的影響,表現(xiàn)出不盡相同又較為相似的回收策略,
而JScript.dll和Carakan則完全沒有這方面的優(yōu)化,會完整保留整個 LexicalEnvironment 中的所有變量綁定,造成一定的內(nèi)存消耗。
由于對閉包內(nèi)變量有回收優(yōu)化策略的 Chakra、V8 和 SpiderMonkey 引擎的行為較為相似,因此可以總結(jié)如下,當(dāng)返回一個函數(shù) fn 時:
- 如果 fn 的 [[Scope]] 是ObjectEnvironment(with 表達(dá)式生成 ObjectEnvironment,函數(shù)和 catch 表達(dá)式生成 DeclarativeEnvironment),則:
- 獲取當(dāng)前 LexicalEnvironment 下的所有類型為 Function 的對象,對于每一個 Function 對象,分析其 FunctionBody:
- 檢查當(dāng)前 LexicalEnvironment 中的每一個變量綁定,如果該綁定有 notSwap 屬性且值為 true,則:
對于Chakra引擎,暫無法得知是按 V8 的模式還是按 SpiderMonkey 的模式進(jìn)行。
如果有?非常龐大?的對象,且預(yù)計會在老舊的引擎?中執(zhí)行,則使用閉包時,注意將閉包不需要的對象置為空引用。
·[建議] 使用?IIFE?避免?Lift?效應(yīng)?。
解釋
在引用函數(shù)外部變量時,函數(shù)執(zhí)行時外部變量的值由運(yùn)行時決定而非定義時,最典型的場景如下:
var?tasks = [];
for?(var?i = 0; i < 5; i++) {
????tasks[tasks.length] = function?() {
????????console.log('Current cursor is at '?+ i);
????};
}
?
var?len = tasks.length;
while?(len--) {
????tasks[len]();
}
?
以上代碼對 tasks 中的函數(shù)的執(zhí)行均會輸出?Current?cursor?is?at?5,往往不符合預(yù)期。
此現(xiàn)象稱為Lift 效應(yīng)?。解決的方式是通過額外加上一層閉包函數(shù),將需要的外部變量作為參數(shù)傳遞來解除變量的綁定關(guān)系:
var?tasks = [];
for?(var?i = 0; i < 5; i++) {
????// 注意有一層額外的閉包
????tasks[tasks.length] = (function?(i) {
????????return?function?() {
????????????console.log('Current cursor is at '?+ i);
????????};
????})(i);
}
?
var?len = tasks.length;
while?(len--) {
????tasks[len]();
}
?
3.8.4. 空函數(shù)
·[建議] 空函數(shù)不使用?new?Function()?的形式。
示例
var?emptyFunction = function?() {};
?
·[建議] 對于性能有高要求的場合,建議存在一個空函數(shù)的常量,供多處使用共享。
示例
var?EMPTY_FUNCTION = function?() {};
?
function?MyClass() {
}
?
MyClass.prototype.abstractMethod = EMPTY_FUNCTION;
MyClass.prototype.hooks.before = EMPTY_FUNCTION;
MyClass.prototype.hooks.after = EMPTY_FUNCTION;
?
3.9. 面向?qū)ο?/span>
·[強(qiáng)制] 類的繼承方案,實(shí)現(xiàn)時需要修正?constructor。
解釋
通常使用其他 library 的類繼承方案都會進(jìn)行 constructor 修正。如果是自己實(shí)現(xiàn)的類繼承方案,需要進(jìn)行 constructor 修正。
示例
/** * 構(gòu)建類之間的繼承關(guān)系 * * @param {Function} subClass 子類函數(shù) * @param {Function} superClass 父類函數(shù) */function?inherits(subClass, superClass) {
????var?F = new?Function();
????F.prototype = superClass.prototype;
????subClass.prototype = new?F();
????subClass.prototype.constructor = subClass;
}
?
·[建議] 聲明類時,保證?constructor?的正確性。
示例
function?Animal(name) {
????this.name = name;
}
?
// 直接prototype等于對象時,需要修正constructor
Animal.prototype = {
????constructor: Animal,
?
????jump: function?() {
????????alert('animal '?+ this.name + ' jump');
????}
};
?
// 這種方式擴(kuò)展prototype則無需理會constructor
Animal.prototype.jump = function?() {
????alert('animal '?+ this.name + ' jump');
};
?
·[建議] 屬性在構(gòu)造函數(shù)中聲明,方法在原型中聲明。
解釋
原型對象的成員被所有實(shí)例共享,能節(jié)約內(nèi)存占用。所以編碼時我們應(yīng)該遵守這樣的原則:原型對象包含程序不會修改的成員,如方法函數(shù)或配置項(xiàng)。
function?TextNode(value, engine) {
????this.value = value;
????this.engine = engine;
}
?
TextNode.prototype.clone = function?() {
????return?this;
};
?
·[強(qiáng)制] 自定義事件的?事件名?必須全小寫。
解釋
在 JavaScript 廣泛應(yīng)用的瀏覽器環(huán)境,絕大多數(shù) DOM 事件名稱都是全小寫的。為了遵循大多數(shù) JavaScript 開發(fā)者的習(xí)慣,在設(shè)計自定義事件時,事件名也應(yīng)該全小寫。
·[強(qiáng)制] 自定義事件只能有一個?event?參數(shù)。如果事件需要傳遞較多信息,應(yīng)仔細(xì)設(shè)計事件對象。
解釋
一個事件對象的好處有:
- 順序無關(guān),避免事件監(jiān)聽者需要記憶參數(shù)順序。
- 每個事件信息都可以根據(jù)需要提供或者不提供,更自由。
- 擴(kuò)展方便,未來添加事件信息時,無需考慮會破壞監(jiān)聽器參數(shù)形式而無法向后兼容。
·[建議] 設(shè)計自定義事件時,應(yīng)考慮禁止默認(rèn)行為。
解釋
常見禁止默認(rèn)行為的方式有兩種:
- 事件監(jiān)聽函數(shù)中 return false。
- 事件對象中包含禁止默認(rèn)行為的方法,如 preventDefault。
3.10. 動態(tài)特性
3.10.1. eval
·[強(qiáng)制] 避免使用直接?eval?函數(shù)。
解釋
直接?eval,指的是以函數(shù)方式調(diào)用?eval?的調(diào)用方法。直接?eval?調(diào)用執(zhí)行代碼的作用域?yàn)楸镜刈饔糜?#xff0c;應(yīng)當(dāng)避免。
如果有特殊情況需要使用直接?eval?,需在代碼中用詳細(xì)的注釋說明為何必須使用直接?eval?,
不能使用其它動態(tài)執(zhí)行代碼的方式,同時需要其他資深工程師進(jìn)行 Code Review。
·[建議] 盡量避免使用?eval?函數(shù)。
3.10.2. 動態(tài)執(zhí)行代碼
·[建議] 使用?new?Function?執(zhí)行動態(tài)代碼。
解釋
通過?new?Function?生成的函數(shù)作用域是全局使用域,不會影響當(dāng)當(dāng)前的本地作用域。如果有動態(tài)代碼執(zhí)行的需求,建議使用?new?Function?。
示例
var?handler = new?Function('x', 'y', 'return x + y;');
var?result = handler($('#x').val(), $('#y').val());
?
3.10.3. with
·[建議] 盡量不要使用?with。
解釋
使用?with?可能會增加代碼的復(fù)雜度,不利于閱讀和管理;也會對性能有影響。大多數(shù)使用?with?的場景都能使用其他方式較好的替代。所以,盡量不要使用?with。
3.10.4. delete
·[建議] 減少?delete?的使用。
解釋
如果沒有特別的需求,減少或避免使用?delete?。delete?的使用會破壞部分 JavaScript 引擎的性能優(yōu)化。
·[建議] 處理?delete?可能產(chǎn)生的異常。
解釋
對于有被遍歷需求,且值 null 被認(rèn)為具有業(yè)務(wù)邏輯意義的值的對象,移除某個屬性必須使用 delete 操作。
在嚴(yán)格模式或IE下使用 delete 時,不能被刪除的屬性會拋出異常,因此在不確定屬性是否可以刪除的情況下,建議添加 try-catch 塊。
示例
try?{
????delete?o.x;
}
catch?(deleteError) {
????o.x = null;
}
?
3.10.5. 對象屬性
·[建議] 避免修改外部傳入的對象。
解釋
JavaScript 因其腳本語言的動態(tài)特性,當(dāng)一個對象未被 seal 或 freeze 時,可以任意添加、刪除、修改屬性值。
但是隨意地對 非自身控制的對象 進(jìn)行修改,很容易造成代碼在不可預(yù)知的情況下出現(xiàn)問題。因此,設(shè)計良好的組件、函數(shù)應(yīng)該避免對外部傳入的對象的修改。
下面代碼的 selectNode 方法修改了由外部傳入的 datasource 對象。如果 datasource 用在其它場合(如另一個 Tree 實(shí)例)下,會造成狀態(tài)的混亂。
function?Tree(datasource) {
????this.datasource = datasource;
}
?
Tree.prototype.selectNode = function?(id) {
????// 從datasource中找出節(jié)點(diǎn)對象
????var?node = this.findNode(id);
????if?(node) {
????????node.selected = true;
????????this.flushView();
????}
};
?
對于此類場景,需要使用額外的對象來維護(hù),使用由自身控制,不與外部產(chǎn)生任何交互的 selectedNodeIndex 對象來維護(hù)節(jié)點(diǎn)的選中狀態(tài),不對 datasource 作任何修改。
function?Tree(datasource) {
????this.datasource = datasource;
????this.selectedNodeIndex = {};
}
?
Tree.prototype.selectNode = function?(id) {
????// 從datasource中找出節(jié)點(diǎn)對象
????var?node = this.findNode(id);
????if?(node) {
????????this.selectedNodeIndex[id] = true;
????????this.flushView();
????}
};
?
除此之外,也可以通過 deepClone 等手段將自身維護(hù)的對象與外部傳入的分離,保證不會相互影響。
·[建議] 具備強(qiáng)類型的設(shè)計。
解釋
- 如果一個屬性被設(shè)計為 boolean 類型,則不要使用 1 / 0 作為其值。對于標(biāo)識性的屬性,如對代碼體積有嚴(yán)格要求,可以從一開始就設(shè)計為 number 類型且將 0 作為否定值。
- 從 DOM 中取出的值通常為 string 類型,如果有對象或函數(shù)的接收類型為 number 類型,提前作好轉(zhuǎn)換,而不是期望對象、函數(shù)可以處理多類型的值。
4. 瀏覽器環(huán)境
4.1. 模塊化
4.1.1. AMD
·[強(qiáng)制] 使用?AMD?作為模塊定義。
解釋
AMD 作為由社區(qū)認(rèn)可的模塊定義形式,提供多種重載提供靈活的使用方式,并且絕大多數(shù)優(yōu)秀的 Library 都支持 AMD,適合作為規(guī)范。
目前,比較成熟的 AMD Loader 有:
- 官方實(shí)現(xiàn)的?requirejs
- 百度自己實(shí)現(xiàn)的?esl
·[強(qiáng)制] 模塊?id?必須符合標(biāo)準(zhǔn)。
解釋
模塊?id?必須符合以下約束條件:
- 類型為 string,并且是由?/?分割的一系列 terms 來組成。例如:this/is/a/module。
- term 應(yīng)該符合?[a-zA-Z0-9_-]+?規(guī)則。
- 不應(yīng)該有 .js 后綴。
- 跟文件的路徑保持一致。
4.1.2. define
·[建議] 定義模塊時不要指明?id?和?dependencies?。
解釋
在 AMD 的設(shè)計思想里,模塊名稱是和所在路徑相關(guān)的,匿名的模塊更利于封包和遷移。模塊依賴應(yīng)在模塊定義內(nèi)部通過 local require 引用。
所以,推薦使用 define(factory) 的形式進(jìn)行模塊定義。
示例
?
define(
????function?(require) {
????}
);
?
·[建議] 使用?return?來返回模塊定義。
解釋
使用 return 可以減少 factory 接收的參數(shù)(不需要接收 exports 和 module),在沒有 AMD Loader 的場景下也更容易進(jìn)行簡單的處理來偽造一個 Loader。
示例
?
define(
????function?(require) {
????????var?exports = {};
?
????????// ...
?
????????return?exports;
????}
);
?
4.1.3. require
·[強(qiáng)制] 全局運(yùn)行環(huán)境中,require?必須以?async?require?形式調(diào)用。
解釋
模塊的加載過程是異步的,同步調(diào)用并無法保證得到正確的結(jié)果。
示例
// good
require(['foo'], function?(foo) {
});
?
// badvar?foo = require('foo');
?
·[強(qiáng)制] 模塊定義中只允許使用?local?require?,不允許使用?global?require?。
解釋
- 在模塊定義中使用 global require,對封裝性是一種破壞。
- 在 AMD 里,global require 是可以被重命名的。并且 Loader 甚至沒有全局的 require 變量,而是用 Loader 名稱做為 global require。模塊定義不應(yīng)該依賴使用的 Loader。
·[強(qiáng)制] Package在實(shí)現(xiàn)時,內(nèi)部模塊的?require?必須使用?relative?id?。
解釋
對于任何可能通過 發(fā)布-引入 的形式復(fù)用的第三方庫、框架、包,開發(fā)者所定義的名稱不代表使用者使用的名稱。因此不要基于任何名稱的假設(shè)。在實(shí)現(xiàn)源碼中,require 自身的其它模塊時使用 relative id。
示例
?
define(
????function?(require) {
????????var?util = require('./util');
????}
);
?
·[建議] 不會被調(diào)用的依賴模塊,在?factory?開始處統(tǒng)一?require。
解釋
有些模塊是依賴的模塊,但不會在模塊實(shí)現(xiàn)中被直接調(diào)用,最為典型的是?css?/?js?/?tpl?等 Plugin 所引入的外部內(nèi)容。此類內(nèi)容建議放在模塊定義最開始處統(tǒng)一引用。
示例
?
define(
????function?(require) {
????????require('css!foo.css');
????????require('tpl!bar.tpl.html');
?
????????// ...
????}
);
?
4.2. DOM
4.2.1. 元素獲取
·[建議] 對于單個元素,盡可能使用?document.getElementById?獲取,避免使用?document.all?。
·[建議] 對于多個元素的集合,盡可能使用?context.getElementsByTagName?獲取。其中?context?可以為?document?或其他元素。指定 tagName 參數(shù)為?*?可以獲得所有子元素。
·[建議] 遍歷元素集合時,盡量緩存集合長度。如需多次操作同一集合,則應(yīng)將集合轉(zhuǎn)為數(shù)組。
解釋
原生獲取元素集合的結(jié)果并不直接引用 DOM 元素,而是對索引進(jìn)行讀取,所以 DOM 結(jié)構(gòu)的改變會實(shí)時反映到結(jié)果中。
示例
?
<div></div>
<span></span>
?
<script>
var elements = document.getElementsByTagName('*');
?
// 顯示為 DIV
alert(elements[0].tagName);
?
var div = elements[0];
var p = document.createElement('p');
docpment.body.insertBefore(p, div);
?
// 顯示為 P
alert(elements[0].tagName);
</script>
?
·[建議] 獲取元素的直接子元素時使用?children。避免使用?childNodes?,除非預(yù)期是需要包含文本、注釋和屬性類型的節(jié)點(diǎn)。
4.2.2. 樣式獲取
·[建議] 獲取元素實(shí)際樣式信息時,應(yīng)使用?getComputedStyle?或?currentStyle?。
解釋
通過 style 只能獲得內(nèi)聯(lián)定義或通過 JavaScript 直接設(shè)置的樣式。通過 CSS class 設(shè)置的元素樣式無法直接通過 style 獲取。
4.2.3. 樣式設(shè)置
·[建議] 盡可能通過為元素添加預(yù)定義的 className 來改變元素樣式,避免直接操作 style 設(shè)置。
·[強(qiáng)制] 通過 style 對象設(shè)置元素樣式時,對于帶單位非 0 值的屬性,不允許省略單位。
解釋
除了 IE,標(biāo)準(zhǔn)瀏覽器會忽略不規(guī)范的屬性值,導(dǎo)致兼容性問題。
4.2.4. DOM 操作
·[建議] 操作?DOM?時,盡量減少頁面?reflow。
解釋
頁面 reflow 是非常耗時的行為,非常容易導(dǎo)致性能瓶頸。下面一些場景會觸發(fā)瀏覽器的reflow:
- DOM元素的添加、修改(內(nèi)容)、刪除。
- 應(yīng)用新的樣式或者修改任何影響元素布局的屬性。
- Resize瀏覽器窗口、滾動頁面。
- 讀取元素的某些屬性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。
·[建議] 盡量減少?DOM?操作。
解釋
DOM 操作也是非常耗時的一種操作,減少 DOM 操作有助于提高性能。舉一個簡單的例子,構(gòu)建一個列表。我們可以用兩種方式:
- 在循環(huán)體中 createElement 并 append 到父元素中。
- 在循環(huán)體中拼接 HTML 字符串,循環(huán)結(jié)束后寫父元素的 innerHTML。
第一種方法看起來比較標(biāo)準(zhǔn),但是每次循環(huán)都會對 DOM 進(jìn)行操作,性能極低。在這里推薦使用第二種方法。
4.2.5. DOM 事件
·[建議] 優(yōu)先使用?addEventListener?/?attachEvent?綁定事件,避免直接在 HTML 屬性中或 DOM 的?expando?屬性綁定事件處理。
解釋
expando 屬性綁定事件容易導(dǎo)致互相覆蓋。
·[建議] 使用?addEventListener?時第三個參數(shù)使用?false。
解釋
標(biāo)準(zhǔn)瀏覽器中的 addEventListener 可以通過第三個參數(shù)指定兩種時間觸發(fā)模型:冒泡和捕獲。而 IE 的 attachEvent 僅支持冒泡的事件觸發(fā)。所以為了保持一致性,通常 addEventListener 的第三個參數(shù)都為 false。
·[建議] 在沒有事件自動管理的框架支持下,應(yīng)持有監(jiān)聽器函數(shù)的引用,在適當(dāng)時候(元素釋放、頁面卸載等)移除添加的監(jiān)聽器。
?
轉(zhuǎn)載于:https://www.cnblogs.com/catEatBird/p/6918324.html
總結(jié)
以上是生活随笔為你收集整理的Javascript编码规范,好的代码从书写规范开始,增强代码的可读性,可维护性,这是相当重要的!...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NSURLConnection和NSRu
- 下一篇: vue基础整理-组件