前端開發規範

1 前言

JavaScript 在百度一直有着普遍的應用,特別是在瀏覽器端的行爲管理。本文檔的目標是使 JavaScript 代碼風格保持一致,容易被理解和被維護。css

雖然本文檔是針對 JavaScript 設計的,可是在使用各類 JavaScript 的預編譯語言時(如 TypeScript 等)時,適用的部分也應儘可能遵循本文檔的約定。html

2 代碼風格

2.1 文件

[建議] JavaScript 文件使用無 BOM 的 UTF-8 編碼。

解釋:前端

UTF-8 編碼具備更普遍的適應性。BOM 在使用程序或工具處理文件時可能形成沒必要要的干擾。node

[建議] 在文件結尾處,保留一個空行。

2.2 結構

2.2.1 縮進

[強制] 使用 4 個空格作爲一個縮進層級,不容許使用 2 個空格 或 tab 字符。
[強制] switch 下的 case 和 default 必須增長一個縮進層級。

示例:git

// good
switch (variable) { case '1': // do... break; case '2': // do... break; default: // do... } // bad switch (variable) { case '1': // do... break; case '2': // do... break; default: // do... }

2.2.2 空格

[強制] 二元運算符兩側必須有一個空格,一元運算符與操做對象之間不容許有空格。

示例:github

var a = !arr.length; a++; a = b + c;
[強制] 用做代碼塊起始的左花括號 { 前必須有一個空格。

示例:ajax

// good
if (condition) { } while (condition) { } function funcName() { } // bad if (condition){ } while (condition){ } function funcName(){ }
[強制] if / else / for / while / function / switch / do / try / catch / finally 關鍵字後,必須有一個空格。

示例:正則表達式

// good
if (condition) { } while (condition) { } (function () { })(); // bad if(condition) { } while(condition) { } (function() { })();
[強制] 在對象建立時,屬性中的 : 以後必須有空格,: 以前不容許有空格。

示例:算法

// good
var obj = { a: 1, b: 2, c: 3 }; // bad var obj = { a : 1, b:2, c :3 };
[強制] 函數聲明、具名函數表達式、函數調用中,函數名和 ( 之間不容許有空格。

示例:編程

// good
function funcName() { } var funcName = function funcName() { }; funcName(); // bad function funcName () { } var funcName = function funcName () { }; funcName ();
[強制] , 和 ; 前不容許有空格。若是不位於行尾,, 和 ; 後必須跟一個空格。

示例:

// good
callFunc(a, b); // bad callFunc(a , b) ;
[強制] 在函數調用、函數聲明、括號表達式、屬性訪問、if / for / while / switch / catch 等語句中,() 和 [] 內緊貼括號部分不容許有空格。

示例:

// 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-- ) { }
[強制] 單行聲明的數組與對象,若是包含元素,{} 和 [] 內緊貼括號部分不容許包含空格。

解釋:

聲明包含元素的數組與對象,只有當內部元素的形式較爲簡單時,才容許寫在一行。元素複雜的狀況,仍是應該換行書寫。

示例:

// good
var arr1 = []; var arr2 = [1, 2, 3]; var obj1 = {}; var obj2 = {name: 'obj'}; var obj3 = { name: 'obj', age: 20, sex: 1 }; // bad var arr1 = [ ]; var arr2 = [ 1, 2, 3 ]; var obj1 = { }; var obj2 = { name: 'obj' }; var obj3 = {name: 'obj', age: 20, sex: 1};
[強制] 行尾不得有多餘的空格。

2.2.3 換行

[強制] 每一個獨立語句結束後必須換行。
[強制] 每行不得超過 120 個字符。

解釋:

超長的不可分割的代碼容許例外,好比複雜的正則表達式。長字符串不在例外之列。

[強制] 運算符處換行時,運算符必須在新行的行首。

示例:

// good
if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin') ) { // Code } var result = number1 + number2 + number3 + number4 + number5; // bad if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin')) { // Code } var result = number1 + number2 + number3 + number4 + number5;
[強制] 在函數聲明、函數表達式、函數調用、對象建立、數組建立、for 語句等場景中,不容許在 , 或 ; 前換行。

示例:

// good
var obj = { a: 1, b: 2, c: 3 }; foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback ); // bad var obj = { a: 1 , b: 2 , c: 3 }; foo( aVeryVeryLongArgument , anotherVeryLongArgument , callback );
[建議] 不一樣行爲或邏輯的語句集,使用空行隔開,更易閱讀。

示例:

// 僅爲按邏輯換行的示例,不表明setStyle的最優實現
function setStyle(element, property, value) { if (element == null) { return; } element.style[property] = value; }
[建議] 在語句的行長度超過 120 時,根據邏輯條件合理縮進。

示例:

// 較複雜的邏輯條件組合,將每一個條件獨立一行,邏輯運算符放置在行首進行分隔,或將部分邏輯按邏輯組合進行分隔。
// 建議最終將右括號 ) 與左大括號 { 放在獨立一行,保證與 `if` 內語句塊能容易視覺辨識。 if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin') ) { // Code } // 按必定長度截斷字符串,並使用 + 運算符進行鏈接。 // 分隔字符串儘可能按語義進行,如不要在一個完整的名詞中間斷開。 // 特別的,對於 HTML 片斷的拼接,經過縮進,保持和 HTML 相同的結構。 var html = '' // 此處用一個空字符串,以便整個 HTML 片斷都在新行嚴格對齊 + '<article>' + '<h1>Title here</h1>' + '<p>This is a paragraph</p>' + '<footer>Complete</footer>' + '</article>'; // 也可以使用數組來進行拼接,相對 `+` 更容易調整縮進。 var html = [ '<article>', '<h1>Title here</h1>', '<p>This is a paragraph</p>', '<footer>Complete</footer>', '</article>' ]; html = html.join(''); // 當參數過多時,將每一個參數獨立寫在一行上,並將結束的右括號 ) 獨立一行。 // 全部參數必須增長一個縮進。 foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback ); // 也能夠按邏輯對參數進行組合。 // 最經典的是 baidu.format 函數,調用時將參數分爲「模板」和「數據」兩塊 baidu.format( dateFormatTemplate, year, month, date, hour, minute, second ); // 當函數調用時,若是有一個或以上參數跨越多行,應當每個參數獨立一行。 // 這一般出如今匿名函數或者對象初始化等做爲參數時,如 `setTimeout` 函數等。 setTimeout( function () { alert('hello'); }, 200 ); order.data.read( 'id=' + me.model.id, function (data) { me.attchToModel(data.result); callback(); }, 300 ); // 鏈式調用較長時採用縮進進行調整。 $('#items') .find('.selected') .highlight() .end(); // 三元運算符由3部分組成,所以其換行應當根據每一個部分的長度不一樣,造成不一樣的狀況。 var result = thisIsAVeryVeryLongCondition ? resultA : resultB; var result = condition ? thisIsAVeryVeryLongResult : resultB; // 數組和對象初始化的混用,嚴格按照每一個對象的 `{` 和結束 `}` 在獨立一行的風格書寫。 var array = [ { // ... }, { // ... } ];
[建議] 對於 if...else...try...catch...finally 等語句,推薦使用在 } 號後添加一個換行 的風格,使代碼層次結構更清晰,閱讀性更好。

示例:

if (condition) {
    // some statements; } else { // some statements; } try { // some statements; } catch (ex) { // some statements; }

2.2.4 語句

[強制] 不得省略語句結束的分號。
[強制] 在 if / else / for / do / while 語句中,即便只有一行,也不得省略塊 {...}

示例:

// good
if (condition) { callFunc(); } // bad if (condition) callFunc(); if (condition) callFunc();
[強制] 函數定義結束不容許添加分號。

示例:

// good
function funcName() { } // bad function funcName() { }; // 若是是函數表達式,分號是不容許省略的。 var funcName = function () { };
[強制] IIFE 必須在函數表達式外添加 (,非 IIFE 不得在函數表達式外添加 (

解釋:

IIFE = Immediately-Invoked Function Expression.

額外的 ( 可以讓代碼在閱讀的一開始就能判斷函數是否當即被調用,進而明白接下來代碼的用途。而不是一直拖到底部才恍然大悟。

示例:

// good
var task = (function () { // Code return result; })(); var func = function () { }; // bad var task = function () { // Code return result; }(); var func = (function () { });

2.3 命名

[強制] 變量 使用 Camel命名法

示例:

var loadingModules = {};
[強制] 常量 使用 所有字母大寫,單詞間下劃線分隔 的命名方式。

示例:

var HTML_ENTITY = {};
[強制] 函數 使用 Camel命名法

示例:

function stringFormat(source) { }
[強制] 函數的 參數 使用 Camel命名法

示例:

function hear(theBells) { }
[強制]  使用 Pascal命名法

示例:

function TextNode(options) { }
[強制] 類的 方法 / 屬性 使用 Camel命名法

示例:

function TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };
[強制] 枚舉變量 使用 Pascal命名法枚舉的屬性 使用 所有字母大寫,單詞間下劃線分隔 的命名方式。

示例:

var TargetState = { READING: 1, READED: 2, APPLIED: 3, READY: 4 };
[強制] 命名空間 使用 Camel命名法

示例:

equipments.heavyWeapons = {};
[強制] 由多個單詞組成的縮寫詞,在命名中,根據當前命名法和出現的位置,全部字母的大小寫與首字母的大小寫保持一致。

示例:

function XMLParser() { } function insertHTML(element, html) { } var httpRequest = new HTTPRequest();
[強制] 類名 使用 名詞

示例:

function Engine(options) { }
[建議] 函數名 使用 動賓短語

示例:

function getStyle(element) { }
[建議] boolean 類型的變量使用 is 或 has 開頭。

示例:

var isReady = false; var hasMoreCommands = false;
[建議] Promise對象 用 動賓短語的進行時 表達。

示例:

var loadingData = ajax.get('url'); loadingData.then(callback);

2.4 註釋

2.4.1 單行註釋

[強制] 必須獨佔一行。// 後跟一個空格,縮進與下一行被註釋說明的代碼一致。

2.4.2 多行註釋

[建議] 避免使用 /*...*/ 這樣的多行註釋。有多行註釋內容時,使用多個單行註釋。

2.4.3 文檔化註釋

[強制] 爲了便於代碼閱讀和自文檔化,如下內容必須包含以 /**...*/ 形式的塊註釋中。

解釋:

  1. 文件
  2. namespace
  3. 函數或方法
  4. 類屬性
  5. 事件
  6. 全局變量
  7. 常量
  8. AMD 模塊
[強制] 文檔註釋前必須空一行。
[建議] 自文檔化的文檔說明 what,而不是 how。

2.4.4 類型定義

[強制] 類型定義都是以 { 開始, 以 } 結束。

解釋:

經常使用類型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。

類型不只侷限於內置的類型,也能夠是自定義的類型。好比定義了一個類 Developer,就可使用它來定義一個參數和返回值的類型。

[強制] 對於基本類型 {string}, {number}, {boolean},首字母必須小寫。
類型定義
語法示例
解釋
String {string} --
Number {number} --
Boolean {boolean} --
Object {Object} --
Function {Function} --
RegExp {RegExp} --
Array {Array} --
Date {Date} --
單一類型集合 {Array.<string>} string 類型的數組
多類型 {(number|boolean)} 多是 number 類型, 也多是 boolean 類型
容許爲null {?number} 多是 number, 也多是 null
不容許爲null {!Object} Object 類型, 但不是 null
Function類型 {function(number, boolean)} 函數, 形參類型
Function帶返回值 {function(number, boolean):string} 函數, 形參, 返回值類型
Promise Promise.<resolveType, rejectType> Promise,成功返回的數據類型,失敗返回的錯誤類型
參數可選 @param {string=} name 可選參數, =爲類型後綴
可變參數 @param {...number} args 變長參數, ...爲類型前綴
任意類型 {*} 任意類型
可選任意類型 @param {*=} name 可選參數,類型不限
可變任意類型 @param {...*} args 變長參數,類型不限

2.4.5 文件註釋

[強制] 文件頂部必須包含文件註釋,用 @file 標識文件說明。

示例:

/**
 * @file Describe the file  */
[建議] 文件註釋中能夠用 @author 標識開發者信息。

解釋:

開發者信息可以體現開發人員對文件的貢獻,而且可以讓遇到問題或但願瞭解相關信息的人找到維護人。一般狀況文件在被建立時標識的是建立者。隨着項目的進展,愈來愈多的人加入,參與這個文件的開發,新的做者應該被加入 @author 標識。

@author 標識具備多人時,原則是按照 責任 進行排序。一般的說就是若是有問題,就是找第一我的應該比找第二我的有效。好比文件的建立者因爲各類緣由,模塊移交給了其餘人或其餘團隊,後來由於新增需求,其餘人在新增代碼時,添加 @author 標識應該把本身的名字添加在建立人的前面。

@author 中的名字不容許被刪除。任何勞動成果都應該被尊重。

業務項目中,一個文件可能被多人頻繁修改,而且每一個人的維護時間均可能不會很長,不建議爲文件增長 @author 標識。經過版本控制系統追蹤變動,按業務邏輯單元肯定模塊的維護責任人,經過文檔與wiki跟蹤和查詢,是更好的責任管理方式。

對於業務邏輯無關的技術型基礎項目,特別是開源的公共項目,應使用 @author 標識。

示例:

/**
 * @file Describe the file  * @author author-name(mail-name@domain.com)  * author-name2(mail-name2@domain.com)  */

2.4.6 命名空間註釋

[建議] 命名空間使用 @namespace 標識。

示例:

/**
 * @namespace  */ var util = {};

2.4.7 類註釋

[建議] 使用 @class 標記類或構造函數。

解釋:

對於使用對象 constructor 屬性來定義的構造函數,可使用 @constructor 來標記。

示例:

/**
 * 描述  *  * @class  */ function Developer() { // constructor body }
[建議] 使用 @extends 標記類的繼承信息。

示例:

/**
 * 描述  *  * @class  * @extends Developer  */ function Fronteer() { Developer.call(this); // constructor body } util.inherits(Fronteer, Developer);
[強制] 使用包裝方式擴展類成員時, 必須經過 @lends 進行從新指向。

解釋:

沒有 @lends 標記將沒法爲該類生成包含擴展類成員的文檔。

示例:

/**
 * 類描述  *  * @class  * @extends Developer  */ function Fronteer() { Developer.call(this); // constructor body } util.extend( Fronteer.prototype, /** @lends Fronteer.prototype */{ getLevel: function () { // TODO } } );
[強制] 類的屬性或方法等成員信息使用 @public / @protected / @private 中的任意一個,指明可訪問性。

解釋:

生成的文檔中將有可訪問性的標記,避免用戶直接使用非 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 函數/方法註釋

[強制] 函數/方法註釋必須包含函數說明,有參數和返回值時必須使用註釋標識。

解釋:

當 return 關鍵字僅做退出函數/方法使用時,無須對返回值做註釋標識。

[強制] 參數和返回值註釋必須包含類型信息,且不容許省略參數的說明。
[建議] 當函數是內部函數,外部不可訪問時,可使用 @inner 標識。

示例:

/**
 * 函數描述  *  * @param {string} p1 參數1的說明  * @param {string} p2 參數2的說明,比較長  * 那就換行了.  * @param {number=} p3 參數3的說明(可選)  * @return {Object} 返回值描述  */ function foo(p1, p2, p3) { var p3 = p3 || 10; return { p1: p1, p2: p2, p3: p3 }; }
[強制] 對 Object 中各項的描述, 必須使用 @param 標識。

示例:

/**
 * 函數描述  *  * @param {Object} option 參數描述  * @param {string} option.url option項描述  * @param {string=} option.method option項描述,可選參數  */ function foo(option) { // TODO }
[建議] 重寫父類方法時, 應當添加 @override 標識。若是重寫的形參個數、類型、順序和返回值類型均未發生變化,可省略 @param@return,僅用 @override 標識,不然仍應做完整註釋。

解釋:

簡而言之,當子類重寫的方法能直接套用父類的方法註釋時可省略對參數與返回值的註釋。

2.4.9 事件註釋

[強制] 必須使用 @event 標識事件,事件參數的標識與方法描述的參數標識相同。

示例:

/**
 * 值變動時觸發  *  * @event Select#change  * @param {Object} e e描述  * @param {string} e.before before描述  * @param {string} e.after after描述  */ this.fire( 'change', { before: 'foo', after: 'bar' } );
[強制] 在會廣播事件的函數前使用 @fires 標識廣播的事件,在廣播事件代碼前使用 @event 標識事件。
[建議] 對於事件對象的註釋,使用 @param 標識,生成文檔時可讀性更好。

示例:

/**
 * 點擊處理  *  * @fires Select#change  * @private  */ Select.prototype.clickHandler = function () { /**  * 值變動時觸發  *  * @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 常量註釋

[強制] 常量必須使用 @const 標記,幷包含說明和類型信息。

示例:

/**
 * 常量說明  *  * @const  * @type {string}  */ var REQUEST_URL = 'myurl.do';

2.4.11 複雜類型註釋

[建議] 對於類型未定義的複雜結構的註釋,可使用 @typedef 標識來定義。

示例:

// `namespaceA~` 能夠換成其它 namepaths 前綴,目的是爲了生成文檔中能顯示 `@typedef` 定義的類型和連接。
/**  * 服務器  *  * @typedef {Object} namespaceA~Server  * @property {string} host 主機  * @property {number} port 端口  */ /**  * 服務器列表  *  * @type {Array.<namespaceA~Server>}  */ var servers = [ { host: '1.2.3.4', port: 8080 }, { host: '1.2.3.5', port: 8081 } ];

2.4.12 AMD 模塊註釋

[強制] AMD 模塊使用 @module 或 @exports 標識。

解釋:

@exports 與 @module 均可以用來標識模塊,區別在於 @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 標識:

define(
    function (require) { /**  * module description.  *  * @module foo  */ var exports = {}; /**  * bar description  *  */ exports.bar = function () { // TODO }; return exports; } );

若是直接使用 factory 的 exports 參數,還能夠:

/**
 * module description.  *  * @module  */ define( function (require, exports) { /**  * bar description  *  */ exports.bar = function () { // TODO }; return exports; } );
[強制] 對於已使用 @module 標識爲 AMD模塊 的引用,在 namepaths 中必須增長 module: 做前綴。

解釋:

namepaths 沒有 module: 前綴時,生成的文檔中將沒法正確生成連接。

示例:

/**
 * 點擊處理  *  * @fires module:Select#change  * @private  */ Select.prototype.clickHandler = function () { /**  * 值變動時觸發  *  * @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 標識構建函數。

示例:

/**
 * A module representing a jacket.  * @module jacket  */ define( function () { /**  * @class  * @alias module:jacket  */ var Jacket = function () { }; return Jacket; } );
[建議] 多模塊定義時,可使用 @exports 標識各個模塊。

示例:

// 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標識。

解釋:

使用 @namespace 而不是 @module 或 @exports 時,對模塊的引用能夠省略 module: 前綴。

[建議] 對於 exports 爲類名的模塊,使用 @class 和 @exports 標識。

示例:

// 只使用 @class Bar 時,類方法和屬性都必須增長 @name Bar#methodName 來標識,與 @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 細節註釋

對於內部實現、不容易理解的邏輯說明、摘要信息等,咱們可能須要編寫細節註釋。

[建議] 細節註釋遵循單行註釋的格式。說明必須換行時,每行是一個單行註釋的起始。

示例:

function foo(p1, p2, opt_p3) { // 這裏對具體內部邏輯進行說明 // 說明太長鬚要換行 for (...) { .... } }
[強制] 有時咱們會使用一些特殊標記進行說明。特殊標記必須使用單行註釋的形式。下面列舉了一些經常使用標記:

解釋:

  1. TODO: 有功能待實現。此時須要對將要實現的功能進行簡單說明。
  2. FIXME: 該處代碼運行沒問題,但可能因爲時間趕或者其餘緣由,須要修正。此時須要對如何修正進行簡單說明。
  3. HACK: 爲修正某些問題而寫的不太好或者使用了某些詭異手段的代碼。此時須要對思路或詭異手段進行描述。
  4. XXX: 該處存在陷阱。此時須要對陷阱進行描述。

3 語言特性

3.1 變量

[強制] 變量、函數在使用前必須先定義。

解釋:

不經過 var 定義變量將致使變量污染全局環境。

示例:

// good
var name = 'MyName'; // bad name = 'MyName';

原則上不建議使用全局變量,對於已有的全局變量或第三方框架引入的全局變量,須要根據檢查工具的語法標識。

示例:

/* globals jQuery */
var element = jQuery('#element-id');
[強制] 每一個 var 只能聲明一個變量。

解釋:

一個 var 聲明多個變量,容易致使較長的行長度,而且在修改時容易形成逗號和分號的混淆。

示例:

// good
var hangModules = []; var missModules = []; var visited = {}; // bad var hangModules = [], missModules = [], visited = {};
[強制] 變量必須 即用即聲明,不得在函數或其它形式的代碼塊起始位置統一聲明全部變量。

解釋:

變量聲明與使用的距離越遠,出現的跨度越大,代碼的閱讀與維護成本越高。雖然JavaScript的變量是函數做用域,仍是應該根據編程中的意圖,縮小變量出現的距離空間。

示例:

// good
function 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; } // bad function 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 條件

[強制] 在 Equality Expression 中使用類型嚴格的 ===。僅當判斷 null 或 undefined 時,容許使用 == null

解釋:

使用 === 能夠避免等於判斷中隱式的類型轉換。

示例:

// good
if (age === 30) { // ...... } // bad if (age == 30) { // ...... }
[建議] 儘量使用簡潔的表達式。

示例:

// 字符串爲空

// good if (!name) { // ...... } // bad if (name === '') { // ...... }
// 字符串非空

// good if (name) { // ...... } // bad if (name !== '') { // ...... }
// 數組非空

// good if (collection.length) { // ...... } // bad if (collection.length > 0) { // ...... }
// 布爾不成立

// good if (!notTrue) { // ...... } // bad if (notTrue === false) { // ...... }
// null 或 undefined

// good if (noValue == null) { // ...... } // bad if (noValue === null || typeof noValue === 'undefined') { // ...... }
[建議] 按執行頻率排列分支的順序。

解釋:

按執行頻率排列分支的順序好處是:

  1. 閱讀的人容易找到最多見的狀況,增長可讀性。
  2. 提升執行效率。
[建議] 對於相同變量或表達式的多值條件,用 switch 代替 if

示例:

// good
switch (typeof variable) { case 'object': // ...... break; case 'number': case 'boolean': case 'string': // ...... break; } // bad var type = typeof variable; if (type === 'object') { // ...... } else if (type === 'number' || type === 'boolean' || type === 'string') { // ...... }
[建議] 若是函數或全局中的 else 塊後沒有任何語句,能夠刪除 else

示例:

// good
function getName() { if (name) { return name; } return 'unnamed'; } // bad function getName() { if (name) { return name; } else { return 'unnamed'; } }

3.3 循環

[建議] 不要在循環體中包含函數表達式,事先將函數提取到循環體外。

解釋:

循環體中的函數表達式,運行過程當中會生成循環次數個函數對象。

示例:

// good
function clicker() { // ...... } for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, 'click', clicker); } // bad for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, 'click', function () {}); }
[建議] 對循環內屢次使用的不變值,在循環外用變量緩存。

示例:

// good
var width = wrap.offsetWidth + 'px'; for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; element.style.width = width; // ...... } // bad for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; element.style.width = wrap.offsetWidth + 'px'; // ...... }
[建議] 對有序集合進行遍歷時,緩存 length

解釋:

雖然現代瀏覽器都對數組長度進行了緩存,但對於一些宿主對象和老舊瀏覽器的數組對象,在每次 length 訪問時會動態計算元素個數,此時緩存 length 能有效提升程序性能。

示例:

for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; // ...... }
[建議] 對有序集合進行順序無關的遍歷時,使用逆序遍歷。

解釋:

逆序遍歷能夠節省變量,代碼比較優化。

示例:

var len = elements.length; while (len--) { var element = elements[len]; // ...... }

3.4 類型

3.4.1 類型檢測

[建議] 類型檢測優先使用 typeof。對象類型檢測使用 instanceofnull 或 undefined 的檢測使用 == null

示例:

// string
typeof variable === 'string' // number typeof variable === 'number' // boolean typeof variable === 'boolean' // Function typeof variable === 'function' // Object typeof variable === 'object' // RegExp variable instanceof RegExp // Array variable instanceof Array // null variable === null // null or undefined variable == null // undefined typeof variable === 'undefined'

3.4.2 類型轉換

[建議] 轉換成 string 時,使用 + ''

示例:

// good
num + ''; // bad new String(num); num.toString(); String(num);
[建議] 轉換成 number 時,一般使用 +

示例:

// good
+str; // bad Number(str);
[建議] string 轉換成 number,要轉換的字符串結尾包含非數字並指望忽略時,使用 parseInt

示例:

var width = '200px'; parseInt(width, 10);
[強制] 使用 parseInt 時,必須指定進制。

示例:

// good
parseInt(str, 10); // bad parseInt(str);
[建議] 轉換成 boolean 時,使用 !!

示例:

var num = 3.14; !!num;
[建議] number 去除小數點,使用 Math.floor / Math.round / Math.ceil,不使用 parseInt

示例:

// good
var num = 3.14; Math.ceil(num); // bad var num = 3.14; parseInt(num, 10);

3.5 字符串

[強制] 字符串開頭和結束使用單引號 '

解釋:

  1. 輸入單引號不須要按住 shift,方便輸入。
  2. 實際使用中,字符串常常用來拼接 HTML。爲方便 HTML 中包含雙引號而不須要轉義寫法。

示例:

var str = '我是一個字符串'; var html = '<div class="cls">拼接HTML能夠省去雙引號轉義</div>';
[建議] 使用 數組 或 + 拼接字符串。

解釋:

  1. 使用 + 拼接字符串,若是拼接的所有是 StringLiteral,壓縮工具能夠對其進行自動合併的優化。因此,靜態字符串建議使用 + 拼接。
  2. 在現代瀏覽器下,使用 + 拼接字符串,性能較數組的方式要高。
  3. 如須要兼顧老舊瀏覽器,應儘可能使用數組拼接字符串。

示例:

// 使用數組拼接字符串
var str = [ // 推薦換行開始並縮進開始第一個字符串, 對齊代碼, 方便閱讀. '<ul>', '<li>第一項</li>', '<li>第二項</li>', '</ul>' ].join(''); // 使用 `+` 拼接字符串 var str2 = '' // 建議第一個爲空字符串, 第二個換行開始並縮進開始, 對齊代碼, 方便閱讀 + '<ul>', + '<li>第一項</li>', + '<li>第二項</li>', + '</ul>';
[建議] 使用字符串拼接的方式生成HTML,須要根據語境進行合理的轉義。

解釋:

在 JavaScript 中拼接,而且最終將輸出到頁面中的字符串,須要進行合理轉義,以防止安全漏洞。下面的示例代碼爲場景說明,不能直接運行。

示例:

// HTML 轉義
var str = '<p>' + htmlEncode(content) + '</p>'; // HTML 轉義 var str = '<input type="text" value="' + htmlEncode(value) + '">'; // URL 轉義 var str = '<a href="/?key=' + htmlEncode(urlEncode(value)) + '">link</a>'; // JavaScript字符串 轉義 + HTML 轉義 var str = '<button onclick="check(\'' + htmlEncode(strLiteral(name)) + '\')">提交</button>';
[建議] 複雜的數據到視圖字符串的轉換過程,選用一種模板引擎。

解釋:

使用模板引擎有以下好處:

  1. 在開發過程當中專一於數據,將視圖生成的過程由另一個層級維護,使程序邏輯結構更清晰。
  2. 優秀的模板引擎,經過模板編譯技術和高質量的編譯產物,能得到比手工拼接字符串更高的性能。
  3. 模板引擎能方便的對動態數據進行相應的轉義,部分模板引擎默認進行HTML轉義,安全性更好。

  • artTemplate: 體積較小,在全部環境下性能高,語法靈活。
  • dot.js: 體積小,在現代瀏覽器下性能高,語法靈活。
  • etpl: 體積較小,在全部環境下性能高,模板複用性高,語法靈活。
  • handlebars: 體積大,在全部環境下性能高,擴展性高。
  • hogon: 體積小,在現代瀏覽器下性能高。
  • nunjucks: 體積較大,性能通常,模板複用性高。

3.6 對象

[強制] 使用對象字面量 {} 建立新 Object

示例:

// good
var obj = {}; // bad var obj = new Object();
[建議] 對象建立時,若是一個對象的全部 屬性 都可以不添加引號,建議全部 屬性 不添加引號。

示例:

var info = { name: 'someone', age: 28 };
[建議] 對象建立時,若是任何一個 屬性 須要添加引號,則全部 屬性 建議添加 '

解釋:

若是屬性不符合 Identifier 和 NumberLiteral 的形式,就須要以 StringLiteral 的形式提供。

示例:

// good
var info = { 'name': 'someone', 'age': 28, 'more-info': '...' }; // bad var info = { name: 'someone', age: 28, 'more-info': '...' };
[強制] 不容許修改和擴展任何原生對象和宿主對象的原型。

示例:

// 如下行爲絕對禁止
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 數組

[強制] 使用數組字面量 [] 建立新數組,除非想要建立的是指定長度的數組。

示例:

// good
var arr = []; // bad var arr = new Array();
[強制] 遍歷數組不使用 for in

解釋:

數組對象可能存在數字之外的屬性, 這種狀況下 for in 不會獲得正確結果。

示例:

var arr = ['a', 'b', 'c']; // 這裏僅做演示, 實際中應使用 Object 類型 arr.other = 'other things'; // 正確的遍歷方式 for (var i = 0, len = arr.length; i < len; i++) { console.log(i); } // 錯誤的遍歷方式 for (var i in arr) { console.log(i); }
[建議] 不由於性能的緣由本身實現數組排序功能,儘可能使用數組的 sort 方法。

解釋:

本身實現的常規排序算法,在性能上並不優於數組默認的 sort 方法。如下兩種場景能夠本身實現排序:

  1. 須要穩定的排序算法,達到嚴格一致的排序結果。
  2. 數據特色鮮明,適合使用桶排。
[建議] 清空數組使用 .length = 0

3.8 函數

3.8.1 函數長度

[建議] 一個函數的長度控制在 50 行之內。

解釋:

將過多的邏輯單元混在一個大函數中,易致使難以維護。一個清晰易懂的函數應該完成單一的邏輯單元。複雜的操做應進一步抽取,經過函數的調用來體現流程。

特定算法等不可分割的邏輯容許例外。

示例:

function syncViewStateOnUserAction() { if (x.checked) { y.checked = true; z.value = ''; } else { y.checked = false; } if (a.value) { warning.innerText = ''; submitButton.disabled = false; } else { warning.innerText = 'Please enter it'; submitButton.disabled = true; } } // 直接閱讀該函數會難以明確其主線邏輯,所以下方是一種更合理的表達方式: function syncViewStateOnUserAction() { syncXStateToView(); checkAAvailability(); } function syncXStateToView() { y.checked = x.checked; if (x.checked) { z.value = ''; } } function checkAAvailability() { if (a.value) { clearWarnignForA(); } else { displayWarningForAMissing(); } }

3.8.2 參數設計

[建議] 一個函數的參數控制在 6 個之內。

解釋:

除去不定長參數之外,函數具有不一樣邏輯意義的參數建議控制在 6 個之內,過多參數會致使維護難度增大。

某些狀況下,如使用 AMD Loader 的 require 加載多個模塊時,其 callback 可能會存在較多參數,所以對函數參數的個數不作強制限制。

[建議] 經過 options 參數傳遞非數據輸入型參數。

解釋:

有些函數的參數並非做爲算法的輸入,而是對算法的某些分支條件判斷之用,此類參數建議經過一個 options 參數傳遞。

以下函數:

/**
 * 移除某個元素  *  * @param {Node} element 須要移除的元素  * @param {boolean} removeEventListeners 是否同時將全部註冊在元素上的事件移除  */ function removeElement(element, removeEventListeners) { element.parent.removeChild(element); if (removeEventListeners) { element.clearEventListeners(); } }

能夠轉換爲下面的簽名:

/**
 * 移除某個元素  *  * @param {Node} element 須要移除的元素  * @param {Object} options 相關的邏輯配置  * @param {boolean} options.removeEventListeners 是否同時將全部註冊在元素上的事件移除  */ function removeElement(element, options) { element.parent.removeChild(element); if (options.removeEventListeners) { element.clearEventListeners(); } }

這種模式有幾個顯著的優點:

  • boolean 型的配置項具有名稱,從調用的代碼上更易理解其表達的邏輯意義。
  • 當配置項有增加時,無需無休止地增長參數個數,不會出現 removeElement(element, true, false, false, 3) 這樣難以理解的調用代碼。
  • 當部分配置參數可選時,多個參數的形式很是難處理重載邏輯,而使用一個 options 對象只需判斷屬性是否存在,實現得以簡化。

3.8.3 閉包

[建議] 在適當的時候將閉包內大對象置爲 null

解釋:

在 JavaScript 中,無需特別的關鍵詞就可使用閉包,一個函數能夠任意訪問在其定義的做用域外的變量。須要注意的是,函數的做用域是靜態的,即在定義時決定,與調用的時機和方式沒有任何關係。

閉包會阻止一些變量的垃圾回收,對於較老舊的 JavaScript 引擎,可能致使外部全部變量均沒法回收。

首先一個較爲明確的結論是,如下內容會影響到閉包內變量的回收:

  • 嵌套的函數中是否有使用該變量。
  • 嵌套的函數中是否有 直接調用eval
  • 是否使用了 with 表達式。

Chakra、V8 和 SpiderMonkey 將受以上因素的影響,表現出不盡相同又較爲類似的回收策略,而 JScript.dll 和 Carakan 則徹底沒有這方面的優化,會完整保留整個 LexicalEnvironment 中的全部變量綁定,形成必定的內存消耗。

因爲對閉包內變量有回收優化策略的 Chakra、V8 和 SpiderMonkey 引擎的行爲較爲類似,所以能夠總結以下,當返回一個函數 fn 時:

  1. 若是 fn 的 [[Scope]] 是 ObjectEnvironment(with 表達式生成 ObjectEnvironment,函數和 catch 表達式生成 DeclarativeEnvironment),則:
    1. 若是是 V8 引擎,則退出全過程。
    2. 若是是 SpiderMonkey,則處理該 ObjectEnvironment 的外層 LexicalEnvironment。
  2. 獲取當前 LexicalEnvironment 下的全部類型爲 Function 的對象,對於每個 Function 對象,分析其 FunctionBody:
    1. 若是 FunctionBody 中含有 直接調用 eval,則退出全過程。
    2. 不然獲得全部的 Identifier。
    3. 對於每個 Identifier,設其爲 name,根據查找變量引用的規則,從 LexicalEnvironment 中找出名稱爲 name 的綁定 binding。
    4. 對 binding 添加 notSwap 屬性,其值爲 true
  3. 檢查當前 LexicalEnvironment 中的每個變量綁定,若是該綁定有 notSwap 屬性且值爲 true,則:
    1. 若是是 V8 引擎,刪除該綁定。
    2. 若是是 SpiderMonkey,將該綁定的值設爲 undefined,將刪除 notSwap 屬性。

對於 Chakra 引擎,暫沒法得知是按 V8 的模式仍是按 SpiderMonkey 的模式進行。

若是有 很是龐大 的對象,且預計會在 老舊的引擎 中執行,則使用閉包時,注意將閉包不須要的對象置爲空引用。

[建議] 使用 IIFE 避免 Lift 效應

解釋:

在引用函數外部變量時,函數執行時外部變量的值由運行時決定而非定義時,最典型的場景以下:

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 中的函數的執行均會輸出 Current cursor is at 5,每每不符合預期。

此現象稱爲 Lift 效應 。解決的方式是經過額外加上一層閉包函數,將須要的外部變量做爲參數傳遞來解除變量的綁定關係:

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 空函數

[建議] 空函數不使用 new Function() 的形式。

示例:

var emptyFunction = function () {};
[建議] 對於性能有高要求的場合,建議存在一個空函數的常量,供多處使用共享。

示例:

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 面向對象

[強制] 類的繼承方案,實現時須要修正 constructor

解釋:

一般使用其餘 library 的類繼承方案都會進行 constructor 修正。若是是本身實現的類繼承方案,須要進行 constructor 修正。

示例:

/**
 * 構建類之間的繼承關係  *  * @param {Function} subClass 子類函數  * @param {Function} superClass 父類函數  */ 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'); } }; // 這種方式擴展prototype則無需理會constructor Animal.prototype.jump = function () { alert('animal ' + this.name + ' jump'); };
[建議] 屬性在構造函數中聲明,方法在原型中聲明。

解釋:

原型對象的成員被全部實例共享,能節約內存佔用。因此編碼時咱們應該遵照這樣的原則:原型對象包含程序不會修改的成員,如方法函數或配置項。

function TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };
[強制] 自定義事件的 事件名 必須全小寫。

解釋:

在 JavaScript 普遍應用的瀏覽器環境,絕大多數 DOM 事件名稱都是全小寫的。爲了遵循大多數 JavaScript 開發者的習慣,在設計自定義事件時,事件名也應該全小寫。

[強制] 自定義事件只能有一個 event 參數。若是事件須要傳遞較多信息,應仔細設計事件對象。

解釋:

一個事件對象的好處有:

  1. 順序無關,避免事件監聽者須要記憶參數順序。
  2. 每一個事件信息均可以根據須要提供或者不提供,更自由。
  3. 擴展方便,將來添加事件信息時,無需考慮會破壞監聽器參數形式而沒法向後兼容。
[建議] 設計自定義事件時,應考慮禁止默認行爲。

解釋:

常見禁止默認行爲的方式有兩種:

  1. 事件監聽函數中 return false
  2. 事件對象中包含禁止默認行爲的方法,如 preventDefault

3.10 動態特性

3.10.1 eval

[強制] 避免使用直接 eval 函數。

解釋:

直接 eval,指的是以函數方式調用 eval 的調用方法。直接 eval 調用執行代碼的做用域爲本地做用域,應當避免。

若是有特殊狀況須要使用直接 eval,需在代碼中用詳細的註釋說明爲什麼必須使用直接 eval,不能使用其它動態執行代碼的方式,同時須要其餘資深工程師進行 Code Review。

[建議] 儘可能避免使用 eval 函數。

3.10.2 動態執行代碼

[建議] 使用 new Function 執行動態代碼。

解釋:

經過 new Function 生成的函數做用域是全局使用域,不會影響噹噹前的本地做用域。若是有動態代碼執行的需求,建議使用 new Function

示例:

var handler = new Function('x', 'y', 'return x + y;'); var result = handler($('#x').val(), $('#y').val());

3.10.3 with

[建議] 儘可能不要使用 with

解釋:

使用 with 可能會增長代碼的複雜度,不利於閱讀和管理;也會對性能有影響。大多數使用 with 的場景都能使用其餘方式較好的替代。因此,儘可能不要使用 with

3.10.4 delete

[建議] 減小 delete 的使用。

解釋:

若是沒有特別的需求,減小或避免使用 deletedelete 的使用會破壞部分 JavaScript 引擎的性能優化。

[建議] 處理 delete 可能產生的異常。

解釋:

對於有被遍歷需求,且值 null 被認爲具備業務邏輯意義的值的對象,移除某個屬性必須使用 delete 操做。

在嚴格模式或 IE 下使用 delete 時,不能被刪除的屬性會拋出異常,所以在不肯定屬性是否能夠刪除的狀況下,建議添加 try-catch 塊。

示例:

try {
    delete o.x; } catch (deleteError) { o.x = null; }

3.10.5 對象屬性

[建議] 避免修改外部傳入的對象。

解釋:

JavaScript 因其腳本語言的動態特性,當一個對象未被 seal 或 freeze 時,能夠任意添加、刪除、修改屬性值。

可是隨意地對 非自身控制的對象 進行修改,很容易形成代碼在不可預知的狀況下出現問題。所以,設計良好的組件、函數應該避免對外部傳入的對象的修改。

下面代碼的 selectNode 方法修改了由外部傳入的 datasource 對象。若是 datasource 用在其它場合(如另外一個 Tree 實例)下,會形成狀態的混亂。

function Tree(datasource) { this.datasource = datasource; } Tree.prototype.selectNode = function (id) { // 從datasource中找出節點對象 var node = this.findNode(id); if (node) { node.selected = true; this.flushView(); } };

對於此類場景,須要使用額外的對象來維護,使用由自身控制,不與外部產生任何交互的 selectedNodeIndex 對象來維護節點的選中狀態,不對 datasource 做任何修改。

function Tree(datasource) { this.datasource = datasource; this.selectedNodeIndex = {}; } Tree.prototype.selectNode = function (id) { // 從datasource中找出節點對象 var node = this.findNode(id); if (node) { this.selectedNodeIndex[id] = true; this.flushView(); } };

除此以外,也能夠經過 deepClone 等手段將自身維護的對象與外部傳入的分離,保證不會相互影響。

[建議] 具有強類型的設計。

解釋:

  • 若是一個屬性被設計爲 boolean 類型,則不要使用 1 或 0 做爲其值。對於標識性的屬性,如對代碼體積有嚴格要求,能夠從一開始就設計爲 number 類型且將 0 做爲否認值。
  • 從 DOM 中取出的值一般爲 string 類型,若是有對象或函數的接收類型爲 number 類型,提早做好轉換,而不是指望對象、函數能夠處理多類型的值。

4 瀏覽器環境

4.1 模塊化

4.1.1 AMD

[強制] 使用 AMD 做爲模塊定義。

解釋:

AMD 做爲由社區承認的模塊定義形式,提供多種重載提供靈活的使用方式,而且絕大多數優秀的 Library 都支持 AMD,適合做爲規範。

目前,比較成熟的 AMD Loader 有:

[強制] 模塊 id 必須符合標準。

解釋:

模塊 id 必須符合如下約束條件:

  1. 類型爲 string,而且是由 / 分割的一系列 terms 來組成。例如:this/is/a/module
  2. term 應該符合 [a-zA-Z0-9_-]+ 規則。
  3. 不該該有 .js 後綴。
  4. 跟文件的路徑保持一致。

4.1.2 define

[建議] 定義模塊時不要指明 id 和 dependencies

解釋:

在 AMD 的設計思想裏,模塊名稱是和所在路徑相關的,匿名的模塊更利於封包和遷移。模塊依賴應在模塊定義內部經過 local require 引用。

因此,推薦使用 define(factory) 的形式進行模塊定義。

示例:

define(
    function (require) { } );
[建議] 使用 return 來返回模塊定義。

解釋:

使用 return 能夠減小 factory 接收的參數(不須要接收 exports 和 module),在沒有 AMD Loader 的場景下也更容易進行簡單的處理來僞造一個 Loader。

示例:

define(
    function (require) { var exports = {}; // ... return exports; } );

4.1.3 require

[強制] 全局運行環境中,require 必須以 async require 形式調用。

解釋:

模塊的加載過程是異步的,同步調用並沒有法保證獲得正確的結果。

示例:

// good
require(['foo'], function (foo) { }); // bad var foo = require('foo');
[強制] 模塊定義中只容許使用 local require,不容許使用 global require

解釋:

  1. 在模塊定義中使用 global require,對封裝性是一種破壞。
  2. 在 AMD 裏,global require 是能夠被重命名的。而且 Loader 甚至沒有全局的 require 變量,而是用 Loader 名稱作爲 global require。模塊定義不該該依賴使用的 Loader。
[強制] Package 在實現時,內部模塊的 require 必須使用 relative id

解釋:

對於任何可能經過 發佈-引入 的形式複用的第三方庫、框架、包,開發者所定義的名稱不表明使用者使用的名稱。所以不要基於任何名稱的假設。在實現源碼中,require 自身的其它模塊時使用 relative id

示例:

define(
    function (require) { var util = require('./util'); } );
[建議] 不會被調用的依賴模塊,在 factory 開始處統一 require

解釋:

有些模塊是依賴的模塊,但不會在模塊實現中被直接調用,最爲典型的是 css / js / tpl 等 Plugin 所引入的外部內容。此類內容建議放在模塊定義最開始處統一引用。

示例:

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 參數爲 *能夠得到全部子元素。
[建議] 遍歷元素集合時,儘可能緩存集合長度。如需屢次操做同一集合,則應將集合轉爲數組。

解釋:

原生獲取元素集合的結果並不直接引用 DOM 元素,而是對索引進行讀取,因此 DOM 結構的改變會實時反映到結果中。

示例:

<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,除非預期是須要包含文本、註釋和屬性類型的節點。

4.2.2 樣式獲取

[建議] 獲取元素實際樣式信息時,應使用 getComputedStyle 或 currentStyle

解釋:

經過 style 只能得到內聯定義或經過 JavaScript 直接設置的樣式。經過 CSS class 設置的元素樣式沒法直接經過 style 獲取。

4.2.3 樣式設置

[建議] 儘量經過爲元素添加預約義的 className 來改變元素樣式,避免直接操做 style 設置。
[強制] 經過 style 對象設置元素樣式時,對於帶單位非 0 值的屬性,不容許省略單位。

解釋:

除了 IE,標準瀏覽器會忽略不規範的屬性值,致使兼容性問題。

4.2.4 DOM 操做

[建議] 操做 DOM 時,儘可能減小頁面 reflow

解釋:

頁面 reflow 是很是耗時的行爲,很是容易致使性能瓶頸。下面一些場景會觸發瀏覽器的reflow:

  • DOM元素的添加、修改(內容)、刪除。
  • 應用新的樣式或者修改任何影響元素佈局的屬性。
  • Resize瀏覽器窗口、滾動頁面。
  • 讀取元素的某些屬性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。
[建議] 儘可能減小 DOM 操做。

解釋:

DOM 操做也是很是耗時的一種操做,減小 DOM 操做有助於提升性能。舉一個簡單的例子,構建一個列表。咱們能夠用兩種方式:

  1. 在循環體中 createElement 並 append 到父元素中。
  2. 在循環體中拼接 HTML 字符串,循環結束後寫父元素的 innerHTML。

第一種方法看起來比較標準,可是每次循環都會對 DOM 進行操做,性能極低。在這裏推薦使用第二種方法。

4.2.5 DOM 事件

[建議] 優先使用 addEventListener / attachEvent 綁定事件,避免直接在 HTML 屬性中或 DOM 的 expando 屬性綁定事件處理。

解釋:

expando 屬性綁定事件容易致使互相覆蓋。

[建議] 使用 addEventListener 時第三個參數使用 false

解釋:

標準瀏覽器中的 addEventListener 能夠經過第三個參數指定兩種時間觸發模型:冒泡和捕獲。而 IE 的 attachEvent 僅支持冒泡的事件觸發。因此爲了保持一致性,一般 addEventListener 的第三個參數都爲 false。

[建議] 在沒有事件自動管理的框架支持下,應持有監聽器函數的引用,在適當時候(元素釋放、頁面卸載等)移除添加的監聽器。

前端開發規範.docx

相關文章
相關標籤/搜索