Javascript編碼規範建議

這段時間在整理前端代碼規範,現將JS部分的內容整理,都是基本基礎的內容。請各位大神斧正!!
目前已經整理以下的代碼規範:HTML編碼規範CSSS編碼規範CSS規範--BEM入門javascript

1. 代碼風格

1.1 文件

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

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

1.2 結構

1.2.1 縮進

[強制] 使用 4 個空格作爲一個縮進層級,不容許使用 2 個空格 或 tab 字符。

[強制] switch 下的 case 和 default 必須增長一個縮進層級。

示例:html

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

1.2.2空格

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

示例:前端

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

[強制] 在對象建立時,屬性中的 : 以後必須有空格, : 以前不容許有空格。

示例:java

// good
var obj = {
    a: 1,
    b: 2,
    c: 3
};

------------

// bad
var obj = {
    a : 1,
    b:2,
    c :3
};

[強制] ,; 前不容許有空格。若是不位於行尾; 後必須跟一個空格。

示例:node

// good
callFunc(a, b);

------------
// bad
callFunc(a , b) ;

[強制] 單行聲明的數組與對象,若是包含元素, {} 和 [] 內緊貼括號部分不容許包含空格。

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

// 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 (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
function funcName() {
}
var funcName = function funcName() {
};
funcName();

------------

// bad
function funcName () {
}
var funcName = function funcName () {
};
funcName ();

1.2.3換行

[強制] 每一個獨立語句結束後必須換行。

[強制] 每行不得超過 120 個字符。

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

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

示例:

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

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

1.3命名

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

示例:

var loadingModules = {};

[強制] jQuery對象變量,使用$做爲變量名前綴。

示例:

// good
var $sidebar = $('.sidebar');

------------

// bad
var sidebar = $('.sidebar');

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

示例:

var HTML_ENTITY = {};

[強制] 函數 使用 Camel命名法。

示例:

function getStyle(element) {
}

[建議] 函數名 使用 動賓短語。

示例:

function getStyle(element) {
}

[強制] 類 使用 Pascal命名法。

示例:

function TextNode(options) {
}

[強制] 類名 使用 名詞。

示例:

function Engine(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
};

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

示例:

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

[建議] boolean 類型的變量使用 is 或 has 開頭。

示例:

var isReady = false;
var hasMoreCommands = false;

[建議] Promise對象 用 動賓短語的進行時 表達。

示例:

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

[建議] 不保存this引用,使用Function#bind。

示例:

// good
function () {
    return function () {
        console.log(this);
    }.bind(this);
}
// bad
function () {
    var self = this;
    return function () {
        console.log(self);
    };
}
// bad
function () {
    var that = this;
    return function () {
        console.log(that);
    };
}
// bad
function () {
    var _this = this;
    return function () {
        console.log(_this);
    };
}

1.4 註釋

1.4.1 單行註釋

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

1.4.2多行註釋

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

1.4.3文檔化註釋

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

解釋:

  • 文件

  • namespace

  • 函數或方法

  • 類屬性

  • 事件

  • 全局變量

  • 常量

[強制] 文檔註釋前必須空一行。

[建議] 自文檔化的文檔說明 what,而不是 how。

1.4.4類型定義

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

解釋:
經常使用類型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}
類型不只侷限於內置的類型,也能夠是自定義的類型。好比定義了一個類 Developer,就可使用它來定義一個參數和返回值的類型。

[強制] 對於基本類型 {string}, {number}, {boolean},首字母必須小寫。

示例:
類型描述

1.4.5 文件註釋

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

示例:

/**
 * @file Describe the file
 */

1.4.6 類註釋

[強制] 類的屬性或方法等成員信息不是 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 () {
};

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

示例:

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

[建議] 使用 @extends 標記類的繼承信息。

示例:

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

1.4.7 函數/方法註釋

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

解釋:
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
    };
}

[強制] 對 Object 中各項的描述, 必須使用 @param 標識。

示例:

/**
 * 函數描述
 *
 * @param {Object} option 參數描述
 * @param {string} option.url option項描述
 * @param {string=} option.method option項描述,可選參數
 */
function foo(option) {
    // TODO
}

1.4.8 事件註釋

[強制] 必須使用 @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'
    }
);

[建議] 對於事件對象的註釋,使用 @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'
        }
    );
};

1.4.9 常量註釋

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

示例:

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

1.4.10 細節註釋

[強制] 有時咱們會使用一些特殊標記進行說明。特殊標記必須使用單行註釋的形式。下面列舉了一些經常使用標記:

解釋:

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

  2. FIXME: 該處代碼運行沒問題,但可能因爲時間趕或者其餘緣由,須要修正。此時須要對如何修正進行簡單說明。

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

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

2. 語言特性

2.1 變量

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

解釋:
不經過 var 定義變量將致使變量污染全局環境。
示例:

// good
var name = 'MyName';

------------

// bad
name = 'MyName';

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

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

[建議] 最後再聲明未賦值的變量。當你須要引用前面的變量賦值時這將變的頗有用。
示例:

// good
var items = getItems();
var goSportsTeam = true;
var dragonball;
var length;
var i;
------------

// bad
var i, len, dragonball,
    items = getItems(),
    goSportsTeam = true;
// bad
var i;
var items = getItems();
var dragonball;
var goSportsTeam = true;
var len;

[強制] 變量必須 即用即聲明,不得在函數或其它形式的代碼塊起始位置統一聲明全部變量。

解釋:
變量聲明與使用的距離越遠,出現的跨度越大,代碼的閱讀與維護成本越高。雖然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;
}

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

2.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];
    // ......
}

2.4 類型

2.4.1類型檢測

[建議] 類型檢測優先使用 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'

2.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);

2.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>';

[建議] 程序化生成的字符串使用 Array#join 鏈接而不是使用鏈接符。尤爲是 IE 下;

示例:

var items;
var messages;
var length;
var i;
messages = [{
  state: 'success',
  message: 'This one worked.'
}, {
  state: 'success',
  message: 'This one worked as well.'
}, {
  state: 'error',
  message: 'This one did not work.'
}];
length = messages.length;
// good
function inbox(messages) {
  items = [];
  for (i = 0; i < length; i++) {
    // use direct assignment in this case because we're micro-optimizing.
    items[i] = '<li>' + messages[i].message + '</li>';
  }
  return '<ul>' + items.join('') + '</ul>';
}
// bad
function inbox(messages) {
  items = '<ul>';
  for (i = 0; i < length; i++) {
    items += '<li>' + messages[i].message + '</li>';
  }
  return items + '</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轉義,安全性更好。

介紹幾款模板插件:

  1. artTemplate: 體積較小,在全部環境下性能高,語法靈活。

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

  3. etpl: 體積較小,在全部環境下性能高,模板複用性高,語法靈活。

  4. handlebars: 體積大,在全部環境下性能高,擴展性高。

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

  6. nunjucks: 體積較大,性能通常,模板複用性高。

2.6 對象

[強制] 不容許修改和擴展任何原生對象和宿主對象的原型。

示例:

// 如下行爲絕對禁止
String.prototype.trim = function () {
};

[建議] 使用對象字面量 {} 建立新 Object。

// good
var obj = {};
// bad
var obj = new Object();

[建議] 對象建立時,若是一個對象的全部 屬性 都可以不添加引號,建議全部 屬性 不添加引號。若是任何一個 屬性 須要添加引號,則全部 屬性 建議添加 '。

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

// good
var info = {
    'name': 'someone',
    'age': 28,
    'more-info': '...'
};
// bad
var info = {
    name: 'someone',
    age: 28,
    'more-info': '...'
};

[建議] 屬性訪問時,儘可能使用 .

解釋:
屬性名符合 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];
    }
}

2.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 方法。

[建議] 使用 Array#slice 將類數組對象轉換成數組。

[建議] 清空數組使用 .length = 0。

2.8 函數

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

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

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

  1. boolean 型的配置項具有名稱,從調用的代碼上更易理解其表達的邏輯意義。

  2. 當配置項有增加時,無需無休止地增長參數個數,不會出現 removeElement(element, true, false, false, 3) 這樣難以理解的調用代碼。

  3. 當部分配置參數可選時,多個參數的形式很是難處理重載邏輯,而使用一個 options 對象只需判斷屬性是否存在,實現得以簡化。

[建議] 不要把參數命名爲 arguments。這將取代函數做用域內的 arguments 對象。

示例:

// good
function yup(name, options, args) {
  // ...stuff...
}
// bad
function nope(name, options, arguments) {
  // ...stuff...
}

2.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。

2.10 動態特性

2.10.1 eval

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

解釋:
直接 eval,指的是以函數方式調用 eval 的調用方法。直接 eval 調用執行代碼的做用域爲本地做用域,應當避免。
若是有特殊狀況須要使用直接 eval,需在代碼中用詳細的註釋說明爲什麼必須使用直接 eval,不能使用其它動態執行代碼的方式,同時須要其餘資深工程師進行 Code Review。

2.10.2 with

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

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

2.10.3 delete

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

解釋:
對於有被遍歷需求,且值 null 被認爲具備業務邏輯意義的值的對象,移除某個屬性必須使用 delete 操做。
在嚴格模式或 IE 下使用 delete 時,不能被刪除的屬性會拋出異常,所以在不肯定屬性是否能夠刪除的狀況下,建議添加 try-catch 塊。
示例:

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

2.10.4對象屬性

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

解釋:
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 等手段將自身維護的對象與外部傳入的分離,保證不會相互影響。

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

解釋:

  1. 若是一個屬性被設計爲 boolean 類型,則不要使用 1 或 0 做爲其值。對於標識性的屬性,如對代碼體積有嚴格要求,能夠從一開始就設計爲 number 類型且將 0 做爲否認值。

  2. 從 DOM 中取出的值一般爲 string 類型,若是有對象或函數的接收類型爲 number 類型,提早做好轉換,而不是指望對象、函數能夠處理多類型的值。

相關文章
相關標籤/搜索