JavaScript
文件使用無 BOM
的 UTF-8
編碼。解釋:javascript
UTF-8 編碼具備更普遍的適應性。BOM 在使用程序或工具處理文件時可能形成沒必要要的干擾。html
4
個空格作爲一個縮進層級,不容許使用 2
個空格 或 tab
字符。switch
下的 case
和 default
必須增長一個縮進層級。示例:java
// 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... }
示例:node
var a = !arr.length; a++; a = b + c;
{
前必須有一個空格。示例:git
// good if (condition) { } while (condition) { } function funcName() { } // bad if (condition){ } while (condition){ } function funcName(){ }
if / else / for / while / function / switch / do / try / catch / finally
關鍵字後,必須有一個空格。示例:github
// good if (condition) { } while (condition) { } (function () { })(); // bad if(condition) { } while(condition) { } (function() { })();
:
以後必須有空格,:
以前不容許有空格。示例:ajax
// 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
等語句中,()
和 []
內緊貼括號部分不容許有空格。示例:express
// 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};
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;
,
或 ;
前換行。示例:
// 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; }
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 () { });
變量
使用 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);
//
後跟一個空格,縮進與下一行被註釋說明的代碼一致。/*...*/
這樣的多行註釋。有多行註釋內容時,使用多個單行註釋。/**...*/
形式的塊註釋中。解釋:
{
開始, 以}
結束。解釋:
經常使用類型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。
類型不只侷限於內置的類型,也能夠是自定義的類型。好比定義了一個類 Developer,就可使用它來定義一個參數和返回值的類型。
類型定義 | 語法示例 | 解釋 |
---|---|---|
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} | 函數, 形參, 返回值類型 |
參數可選 | @param {string=} name | 可選參數, =爲類型後綴 |
可變參數 | @param {...number} args | 變長參數, ...爲類型前綴 |
任意類型 | {*} | 任意類型 |
可選任意類型 | @param {*=} name | 可選參數,類型不限 |
可變任意類型 | @param {...*} args | 變長參數,類型不限 |
@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) */
@namespace
標識。示例:
/** * @namespace */ var util = {};
@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 () { };
@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 }; }
@param
標識。示例:
/** * 函數描述 * * @param {Object} option 參數描述 * @param {string} option.url option項描述 * @param {string=} option.method option項描述,可選參數 */ function foo(option) { // TODO }
@override
標識。若是重寫的形參個數、類型、順序和返回值類型均未發生變化,可省略 @param
、@return
,僅用 @override
標識,不然仍應做完整註釋。解釋:
簡而言之,當子類重寫的方法能直接套用父類的方法註釋時可省略對參數與返回值的註釋。
@event
標識事件,事件參數的標識與方法描述的參數標識相同。示例:
/** * 值變動時觸發 * * @event * @param {Object} e e描述 * @param {string} e.before before描述 * @param {string} e.after after描述 */ onchange: function (e) { }
@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' } ); };
@const
標記,幷包含說明和類型信息。示例:
/** * 常量說明 * * @const * @type {string} */ var REQUEST_URL = 'myurl.do';
@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 } ];
對於內部實現、不容易理解的邏輯說明、摘要信息等,咱們可能須要編寫細節註釋。
示例:
function foo(p1, p2) { // 這裏對具體內部邏輯進行說明 // 說明太長鬚要換行 for (...) { .... } }
解釋:
var
定義。解釋:
不經過 var 定義變量將致使變量污染全局環境。
示例:
// good var name = 'MyName'; // bad name = 'MyName';
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; }
===
。僅當判斷 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') { // ...... }
解釋:
按執行頻率排列分支的順序好處是:
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'; } }
解釋:
循環體中的函數表達式,運行過程當中會生成循環次數個函數對象。
示例:
// 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]; // ...... }
typeof
。對象類型檢測使用 instanceof
。null
或 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'
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);
'
。解釋:
示例:
var str = '我是一個字符串'; var html = '<div class="cls">拼接HTML能夠省去雙引號轉義</div>';
數組
或 +
拼接字符串。解釋:
示例:
// 使用數組拼接字符串 var str = [ // 推薦換行開始並縮進開始第一個字符串, 對齊代碼, 方便閱讀. '<ul>', '<li>第一項</li>', '<li>第二項</li>', '</ul>' ].join(''); // 使用 + 拼接字符串 var str2 = '' // 建議第一個爲空字符串, 第二個換行開始並縮進開始, 對齊代碼, 方便閱讀 + '<ul>', + '<li>第一項</li>', + '<li>第二項</li>', + '</ul>';
解釋:
使用模板引擎有以下好處:
{}
建立新 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]; } }
[]
建立新數組,除非想要建立的是指定長度的數組。示例:
// good var arr = []; // bad var arr = new Array();
for in
。解釋:
數組對象可能存在數字之外的屬性, 這種狀況下 for in 不會獲得正確結果.
示例:
var arr = ['a', 'b', 'c']; arr.other = 'other things'; // 這裏僅做演示, 實際中應使用Object類型 // 正確的遍歷方式 for (var i = 0, len = arr.length; i < len; i++) { console.log(i); } // 錯誤的遍歷方式 for (i in arr) { console.log(i); }
sort
方法。解釋:
本身實現的常規排序算法,在性能上並不優於數組默認的 sort 方法。如下兩種場景能夠本身實現排序:
.length = 0
。50
行之內。解釋:
將過多的邏輯單元混在一個大函數中,易致使難以維護。一個清晰易懂的函數應該完成單一的邏輯單元。複雜的操做應進一步抽取,經過函數的調用來體現流程。
特定算法等不可分割的邏輯容許例外。
示例:
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; } } // 直接閱讀該函數會難以明確其主線邏輯,所以下方是一種更合理的表達方式: 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(); } }
6
個之內。解釋:
除去不定長參數之外,函數具有不一樣邏輯意義的參數建議控制在 6 個之內,過多參數會致使維護難度增大。
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(); } }
這種模式有幾個顯著的優點:
null
。解釋:
在 JavaScript 中,無需特別的關鍵詞就可使用閉包,一個函數能夠任意訪問在其定義的做用域外的變量。須要注意的是,函數的做用域是靜態的,即在定義時決定,與調用的時機和方式沒有任何關係。
閉包會阻止一些變量的垃圾回收,對於較老舊的JavaScript引擎,可能致使外部全部變量均沒法回收。
首先一個較爲明確的結論是,如下內容會影響到閉包內變量的回收:
Chakra、V8 和 SpiderMonkey 將受以上因素的影響,表現出不盡相同又較爲類似的回收策略,而JScript.dll和Carakan則徹底沒有這方面的優化,會完整保留整個 LexicalEnvironment 中的全部變量綁定,形成必定的內存消耗。
因爲對閉包內變量有回收優化策略的 Chakra、V8 和 SpiderMonkey 引擎的行爲較爲類似,所以能夠總結以下,當返回一個函數 fn 時:
對於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](); }
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;
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
參數。若是事件須要傳遞較多信息,應仔細設計事件對象。解釋:
一個事件對象的好處有:
解釋:
常見禁止默認行爲的方式有兩種:
eval
函數。解釋:
直接 eval,指的是以函數方式調用 eval 的調用方法。直接 eval 調用執行代碼的做用域爲本地做用域,應當避免。
若是有特殊狀況須要使用直接 eval,需在代碼中用詳細的註釋說明爲什麼必須使用直接 eval,不能使用其它動態執行代碼的方式,同時須要其餘資深工程師進行 Code Review。
eval
函數。new Function
執行動態代碼。解釋:
經過 new Function 生成的函數做用域是全局使用域,不會影響噹噹前的本地做用域。若是有動態代碼執行的需求,建議使用 new Function。
示例:
var handler = new Function('x', 'y', 'return x + y;'); var result = handler($('#x').val(), $('#y').val());
with
。解釋:
使用 with 可能會增長代碼的複雜度,不利於閱讀和管理;也會對性能有影響。大多數使用 with 的場景都能使用其餘方式較好的替代。因此,儘可能不要使用 with。
delete
的使用。解釋:
若是沒有特別的需求,減小或避免使用delete
。delete
的使用會破壞部分 JavaScript 引擎的性能優化。
delete
可能產生的異常。解釋:
對於有被遍歷需求,且值 null 被認爲具備業務邏輯意義的值的對象,移除某個屬性必須使用 delete 操做。
在嚴格模式或IE下使用 delete 時,不能被刪除的屬性會拋出異常,所以在不肯定屬性是否能夠刪除的狀況下,建議添加 try-catch 塊。
示例:
try {
delete o.x; } catch (deleteError) { o.x = null; }
解釋:
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 等手段將自身維護的對象與外部傳入的分離,保證不會相互影響。
解釋:
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
,除非預期是須要包含文本、註釋和屬性類型的節點。getComputedStyle
或 currentStyle
。解釋:
經過 style 只能得到內聯定義或經過 JavaScript 直接設置的樣式。經過 CSS class 設置的元素樣式沒法直接經過 style 獲取。
解釋:
除了 IE,標準瀏覽器會忽略不規範的屬性值,致使兼容性問題。
DOM
時,儘可能減小頁面 reflow
。解釋:
頁面 reflow 是很是耗時的行爲,很是容易致使性能瓶頸。下面一些場景會觸發瀏覽器的reflow:
DOM
操做。解釋:
DOM 操做也是很是耗時的一種操做,減小 DOM 操做有助於提升性能。舉一個簡單的例子,構建一個列表。咱們能夠用兩種方式:
第一種方法看起來比較標準,可是每次循環都會對 DOM 進行操做,性能極低。在這裏推薦使用第二種方法。
addEventListener / attachEvent
綁定事件,避免直接在 HTML 屬性中或 DOM 的 expando
屬性綁定事件處理。解釋:
expando 屬性綁定事件容易致使互相覆蓋。
addEventListener
時第三個參數使用 false
。解釋:
標準瀏覽器中的 addEventListener 能夠經過第三個參數指定兩種時間觸發模型:冒泡和捕獲。而 IE 的 attachEvent 僅支持冒泡的事件觸發。因此爲了保持一致性,一般 addEventListener 的第三個參數都爲 false。