JavaScript編碼規范

      網友投稿 630 2025-04-02

      1 前言


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

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

      2 代碼風格

      2.1 文件

      解釋:

      UTF-8 編碼具有更廣泛的適應性。BOM 在使用程序或工具處理文件時可能造成不必要的干擾。

      2.2 結構

      JavaScript編碼規范

      示例:

      // 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... }

      示例:

      var a = !arr.length; a++; a = b + c;

      示例:

      // good if (condition) { } while (condition) { } function funcName() { } // bad if (condition){ } while (condition){ } function funcName(){ }

      示例:

      // 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) ;

      示例:

      // 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};

      解釋:

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

      示例:

      // 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; }

      示例:

      // 較復雜的邏輯條件組合,將每個條件獨立一行,邏輯運算符放置在行首進行分隔,或將部分邏輯按邏輯組合進行分隔。 // 建議最終將右括號 ) 與左大括號 { 放在獨立一行,保證與 `if` 內語句塊能容易視覺辨識。 if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin') ) { // Code } // 按一定長度截斷字符串,并使用 + 運算符進行連接。 // 分隔字符串盡量按語義進行,如不要在一個完整的名詞中間斷開。 // 特別的,對于 HTML 片段的拼接,通過縮進,保持和 HTML 相同的結構。 var html = '' // 此處用一個空字符串,以便整個 HTML 片段都在新行嚴格對齊 + '

      ' + '

      Title here

      ' + '

      This is a paragraph

      ' + '
      Complete
      ' + '
      '; // 也可使用數組來進行拼接,相對 `+` 更容易調整縮進。 var html = [ '
      ', '

      Title here

      ', '

      This is a paragraph

      ', '
      Complete
      ', '
      ' ]; 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 (condition) { // some statements; } else { // some statements; } try { // some statements; } catch (ex) { // some statements; }

      示例:

      // good if (condition) { callFunc(); } // bad if (condition) callFunc(); if (condition) callFunc();

      示例:

      // good function funcName() { } // bad function funcName() { }; // 如果是函數表達式,分號是不允許省略的。 var funcName = function () { };

      解釋:

      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 命名

      示例:

      var loadingModules = {};

      示例:

      var HTML_ENTITY = {};

      示例:

      function stringFormat(source) { }

      示例:

      function hear(theBells) { }

      示例:

      function TextNode(options) { }

      示例:

      function TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };

      示例:

      var TargetState = { READING: 1, READED: 2, APPLIED: 3, READY: 4 };

      示例:

      equipments.heavyWeapons = {};

      示例:

      function XMLParser() { } function insertHTML(element, html) { } var httpRequest = new HTTPRequest();

      示例:

      function Engine(options) { }

      示例:

      function getStyle(element) { }

      示例:

      var isReady = false; var hasMoreCommands = false;

      示例:

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

      2.4 注釋

      解釋:

      文件

      namespace

      函數或方法

      類屬性

      事件

      全局變量

      常量

      AMD 模塊

      解釋:

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

      類型不僅局限于內置的類型,也可以是自定義的類型。比如定義了一個類 Developer,就可以使用它來定義一個參數和返回值的類型。

      示例:

      /** * @file Describe the file */

      解釋:

      @author 標識具有多人時,原則是按照 責任 進行排序。通常的說就是如果有問題,就是找第一個人應該比找第二個人有效。比如文件的創建者由于各種原因,模塊移交給了其他人或其他團隊,后來因為新增需求,其他人在新增代碼時,添加 @author 標識應該把自己的名字添加在創建人的前面。

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

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

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

      示例:

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

      示例:

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

      解釋:

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

      示例:

      /** * 描述 * * @class */ function Developer() { // constructor body }

      示例:

      /** * 描述 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.inherits(Fronteer, Developer);

      解釋:

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

      示例:

      /** * 類描述 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.extend( Fronteer.prototype, /** @lends Fronteer.prototype */{ getLevel: function () { // TODO } } );

      解釋:

      生成的文檔中將有可訪問性的標記,避免用戶直接使用非 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 () { };

      解釋:

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

      示例:

      /** * 函數描述 * * @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 {Object} option 參數描述 * @param {string} option.url option項描述 * @param {string=} option.method option項描述,可選參數 */ function foo(option) { // TODO }

      解釋:

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

      示例:

      /** * 值變更時觸發 * * @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 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 * @type {string} */ var REQUEST_URL = 'myurl.do';

      示例:

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

      解釋:

      @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; } );

      解釋:

      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' } ); };

      示例:

      /** * A module representing a jacket. * @module jacket */ define( function () { /** * @class * @alias module:jacket */ var Jacket = function () { }; return Jacket; } );

      示例:

      // 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; } );

      解釋:

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

      示例:

      // 只使用 @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 };

      對于內部實現、不容易理解的邏輯說明、摘要信息等,我們可能需要編寫細節注釋。

      示例:

      function foo(p1, p2, opt_p3) { // 這里對具體內部邏輯進行說明 // 說明太長需要換行 for (...) { .... } }

      解釋:

      TODO: 有功能待實現。此時需要對將要實現的功能進行簡單說明。

      FIXME: 該處代碼運行沒問題,但可能由于時間趕或者其他原因,需要修正。此時需要對如何修正進行簡單說明。

      HACK: 為修正某些問題而寫的不太好或者使用了某些詭異手段的代碼。此時需要對思路或詭異手段進行描述。

      XXX: 該處存在陷阱。此時需要對陷阱進行描述。

      3 語言特性

      3.1 變量

      解釋:

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

      示例:

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

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

      示例:

      /* globals jQuery */ var element = jQuery('#element-id');

      解釋:

      一個 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 條件

      解釋:

      使用 === 可以避免等于判斷中隱式的類型轉換。

      示例:

      // 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') { // ...... }

      解釋:

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

      閱讀的人容易找到最常見的情況,增加可讀性。

      提高執行效率。

      示例:

      // 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') { // ...... }

      示例:

      // 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 能有效提高程序性能。

      示例:

      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 類型

      示例:

      // 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'

      示例:

      // good num + ''; // bad new String(num); num.toString(); String(num);

      示例:

      // good +str; // bad Number(str);

      示例:

      var width = '200px'; parseInt(width, 10);

      示例:

      // good parseInt(str, 10); // bad parseInt(str);

      示例:

      var num = 3.14; !!num;

      示例:

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

      3.5 字符串

      解釋:

      輸入單引號不需要按住 shift,方便輸入。

      實際使用中,字符串經常用來拼接 HTML。為方便 HTML 中包含雙引號而不需要轉義寫法。

      示例:

      var str = '我是一個字符串'; var html = '

      拼接HTML可以省去雙引號轉義
      ';

      解釋:

      使用 + 拼接字符串,如果拼接的全部是 StringLiteral,壓縮工具可以對其進行自動合并的優化。所以,靜態字符串建議使用 + 拼接。

      在現代瀏覽器下,使用 + 拼接字符串,性能較數組的方式要高。

      如需要兼顧老舊瀏覽器,應盡量使用數組拼接字符串。

      示例:

      // 使用數組拼接字符串 var str = [ // 推薦換行開始并縮進開始第一個字符串, 對齊代碼, 方便閱讀. '

        ', '
      • 第一項
      • ', '
      • 第二項
      • ', '
      ' ].join(''); // 使用 `+` 拼接字符串 var str2 = '' // 建議第一個為空字符串, 第二個換行開始并縮進開始, 對齊代碼, 方便閱讀 + '
        ', + '
      • 第一項
      • ', + '
      • 第二項
      • ', + '
      ';

      解釋:

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

      示例:

      // HTML 轉義 var str = '

      ' + htmlEncode(content) + '

      '; // HTML 轉義 var str = ''; // URL 轉義 var str = 'link'; // JavaScript字符串 轉義 + HTML 轉義 var str = '';

      解釋:

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

      在開發過程中專注于數據,將視圖生成的過程由另外一個層級維護,使程序邏輯結構更清晰。

      優秀的模板引擎,通過模板編譯技術和高質量的編譯產物,能獲得比手工拼接字符串更高的性能。

      模板引擎能方便的對動態數據進行相應的轉義,部分模板引擎默認進行HTML轉義,安全性更好。

      artTemplate: 體積較小,在所有環境下性能高,語法靈活。

      dot.js: 體積小,在現代瀏覽器下性能高,語法靈活。

      etpl: 體積較小,在所有環境下性能高,模板復用性高,語法靈活。

      handlebars: 體積大,在所有環境下性能高,擴展性高。

      hogon: 體積小,在現代瀏覽器下性能高。

      nunjucks: 體積較大,性能一般,模板復用性高。

      3.6 對象

      示例:

      // 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'];

      示例:

      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 不會得到正確結果。

      示例:

      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 方法。以下兩種場景可以自己實現排序:

      需要穩定的排序算法,達到嚴格一致的排序結果。

      數據特點鮮明,適合使用桶排。

      3.8 函數

      解釋:

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

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

      示例:

      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(); } }

      解釋:

      除去不定長參數以外,函數具備不同邏輯意義的參數建議控制在 6 個以內,過多參數會導致維護難度增大。

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

      解釋:

      有些函數的參數并不是作為算法的輸入,而是對算法的某些分支條件判斷之用,此類參數建議通過一個 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 對象只需判斷屬性是否存在,實現得以簡化。

      解釋:

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

      閉包會阻止一些變量的垃圾回收,對于較老舊的 JavaScript 引擎,可能導致外部所有變量均無法回收。

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

      嵌套的函數中是否有使用該變量。

      嵌套的函數中是否有 直接調用eval。

      是否使用了 with 表達式。

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

      由于對閉包內變量有回收優化策略的 Chakra、V8 和 SpiderMonkey 引擎的行為較為相似,因此可以總結如下,當返回一個函數 fn 時:

      如果 fn 的 [[Scope]] 是 ObjectEnvironment(with 表達式生成 ObjectEnvironment,函數和 catch 表達式生成 DeclarativeEnvironment),則:

      如果是 V8 引擎,則退出全過程。

      如果是 SpiderMonkey,則處理該 ObjectEnvironment 的外層 LexicalEnvironment。

      獲取當前 LexicalEnvironment 下的所有類型為 Function 的對象,對于每一個 Function 對象,分析其 FunctionBody:

      如果 FunctionBody 中含有 直接調用 eval,則退出全過程。

      否則得到所有的 Identifier。

      對于每一個 Identifier,設其為 name,根據查找變量引用的規則,從 LexicalEnvironment 中找出名稱為 name 的綁定 binding。

      對 binding 添加 notSwap 屬性,其值為 true。

      檢查當前 LexicalEnvironment 中的每一個變量綁定,如果該綁定有 notSwap 屬性且值為 true,則:

      如果是 V8 引擎,刪除該綁定。

      如果是 SpiderMonkey,將該綁定的值設為 undefined,將刪除 notSwap 屬性。

      對于 Chakra 引擎,暫無法得知是按 V8 的模式還是按 SpiderMonkey 的模式進行。

      如果有 非常龐大 的對象,且預計會在 老舊的引擎 中執行,則使用閉包時,注意將閉包不需要的對象置為空引用。

      解釋:

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

      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](); }

      示例:

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

      解釋:

      通常使用其他 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; }

      示例:

      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 開發者的習慣,在設計自定義事件時,事件名也應該全小寫。

      解釋:

      一個事件對象的好處有:

      順序無關,避免事件監聽者需要記憶參數順序。

      每個事件信息都可以根據需要提供或者不提供,更自由。

      擴展方便,未來添加事件信息時,無需考慮會破壞-參數形式而無法向后兼容。

      解釋:

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

      事件監聽函數中 return false。

      事件對象中包含禁止默認行為的方法,如 preventDefault。

      3.10 動態特性

      解釋:

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

      如果有特殊情況需要使用直接 eval,需在代碼中用詳細的注釋說明為何必須使用直接 eval,不能使用其它動態執行代碼的方式,同時需要其他資深工程師進行 Code Review。

      解釋:

      通過 new Function 生成的函數作用域是全局使用域,不會影響當當前的本地作用域。如果有動態代碼執行的需求,建議使用 new Function。

      示例:

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

      解釋:

      使用 with 可能會增加代碼的復雜度,不利于閱讀和管理;也會對性能有影響。大多數使用 with 的場景都能使用其他方式較好的替代。所以,盡量不要使用 with。

      解釋:

      如果沒有特別的需求,減少或避免使用 delete。delete 的使用會破壞部分 JavaScript 引擎的性能優化。

      解釋:

      對于有被遍歷需求,且值 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 等手段將自身維護的對象與外部傳入的分離,保證不會相互影響。

      解釋:

      如果一個屬性被設計為 boolean 類型,則不要使用 1 或 0 作為其值。對于標識性的屬性,如對代碼體積有嚴格要求,可以從一開始就設計為 number 類型且將 0 作為否定值。

      從 DOM 中取出的值通常為 string 類型,如果有對象或函數的接收類型為 number 類型,提前作好轉換,而不是期望對象、函數可以處理多類型的值。

      4 瀏覽器環境

      4.1 模塊化

      解釋:

      AMD 作為由社區認可的模塊定義形式,提供多種重載提供靈活的使用方式,并且絕大多數優秀的 Library 都支持 AMD,適合作為規范。

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

      官方實現的 requirejs

      百度自己實現的 esl

      解釋:

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

      類型為 string,并且是由 / 分割的一系列 terms 來組成。例如:this/is/a/module。

      term 應該符合 [a-zA-Z0-9_-]+ 規則。

      不應該有 .js 后綴。

      跟文件的路徑保持一致。

      解釋:

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

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

      示例:

      define( function (require) { } );

      解釋:

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

      示例:

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

      解釋:

      模塊的加載過程是異步的,同步調用并無法保證得到正確的結果。

      示例:

      // good require(['foo'], function (foo) { }); // bad var foo = require('foo');

      解釋:

      在模塊定義中使用 global require,對封裝性是一種破壞。

      在 AMD 里,global require 是可以被重命名的。并且 Loader 甚至沒有全局的 require 變量,而是用 Loader 名稱做為 global require。模塊定義不應該依賴使用的 Loader。

      解釋:

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

      示例:

      define( function (require) { var util = require('./util'); } );

      解釋:

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

      示例:

      define( function (require) { require('css!foo.css'); require('tpl!bar.tpl.html'); // ... } );

      4.2 DOM

      解釋:

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

      示例:

      解釋:

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

      解釋:

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

      解釋:

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

      DOM元素的添加、修改(內容)、刪除。

      應用新的樣式或者修改任何影響元素布局的屬性。

      Resize瀏覽器窗口、滾動頁面。

      讀取元素的某些屬性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。

      解釋:

      DOM 操作也是非常耗時的一種操作,減少 DOM 操作有助于提高性能。舉一個簡單的例子,構建一個列表。我們可以用兩種方式:

      在循環體中 createElement 并 append 到父元素中。

      在循環體中拼接 HTML 字符串,循環結束后寫父元素的 innerHTML。

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

      解釋:

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

      解釋:

      標準瀏覽器中的 addEventListener 可以通過第三個參數指定兩種時間觸發模型:冒泡和捕獲。而 IE 的 attachEvent 僅支持冒泡的事件觸發。所以為了保持一致性,通常 addEventListener 的第三個參數都為 false。

      JavaScript 面向對象編程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:如何在EXCEL中做斜線表頭一個簡單快捷擴展性比較強的方法(excel表如何添加斜線表頭)
      下一篇:Excel合并的單元格怎么計算和值和平均值 Excel教程大全
      相關文章
      亚洲砖码砖专无区2023| 亚洲AV午夜成人片| 亚洲VA中文字幕无码一二三区 | 久久久青草青青亚洲国产免观| 日韩色视频一区二区三区亚洲| 激情内射亚洲一区二区三区爱妻| 亚洲最大福利视频网站| 久久久久久亚洲精品| 亚洲AV无码专区电影在线观看| 亚洲欧洲无码AV电影在线观看| 亚洲人成网亚洲欧洲无码久久| 在线亚洲97se亚洲综合在线| 日日噜噜噜噜夜夜爽亚洲精品| 中文字幕亚洲日韩无线码| 中文亚洲AV片不卡在线观看| 国产精品亚洲片在线观看不卡| 人人狠狠综合久久亚洲88| 久久精品国产亚洲AV麻豆~| 亚洲第一区香蕉_国产a| 亚洲尹人香蕉网在线视颅| 日韩精品一区二区亚洲AV观看 | 亚洲精品免费视频| 久久久亚洲裙底偷窥综合| 亚洲国产精品日韩在线观看| 色偷偷女男人的天堂亚洲网| 亚洲日韩精品无码专区加勒比| 亚洲AV永久无码天堂影院| 亚洲AV无码之日韩精品| 国产亚洲精品影视在线产品 | 亚洲高清一区二区三区电影| 色窝窝亚洲av网| 亚洲日韩精品无码专区网站| 亚洲色精品aⅴ一区区三区| 香蕉蕉亚亚洲aav综合| 亚洲成a人片毛片在线| 亚洲日韩一中文字暮| 亚洲av午夜成人片精品电影| 亚洲精品美女久久久久99| 色婷婷六月亚洲婷婷丁香| 亚洲xxxx18| 无码专区一va亚洲v专区在线|