【規範】前端編碼規範——javascript 規範

全局命名空間污染與 IIFE

老是將代碼包裹成一個 IIFE(Immediately-Invoked Function Expression),用以建立獨立隔絕的定義域。這一舉措可防止全局命名空間被污染。javascript

IIFE 還可確保你的代碼不會輕易被其它全局命名空間裏的代碼所修改(好比第三方庫,window 引用,被覆蓋的未定義的關鍵字等等)。css

不推薦html

var x = 10,
    y = 100;
console.log(window.x + ' ' + window.y);

推薦java

(function(w){
    'use strict';
    var x = 10,
        y = 100;
    w.console.log((w.x === undefined) + ' ' + (w.y === undefined));
}(window));

IIFE(當即執行的函數表達式)

不管什麼時候,想要建立一個新的封閉的定義域,那就用 IIFE。它不只避免了干擾,也使得內存在執行完後當即釋放。git

全部腳本文件建議都從 IIFE 開始。github

當即執行的函數表達式的執行括號應該寫在外包括號內。雖然寫在內仍是寫在外都是有效的,但寫在內使得整個表達式看起來更像一個總體,所以推薦這麼作。編程

不推薦數組

(function(){})();

推薦安全

(function(){}());

因此用下列寫法來格式化你的 IIFE 代碼:閉包

(function($, w, d){
    'use strict';
    $(function() {
        w.alert(d.querySelectorAll('div').length);
    });
}(jQuery, window, document));

嚴格模式

ECMAScript5 嚴格模式可在整個腳本或獨個方法內被激活。它對應不一樣的 javascript 語境會作更加嚴格的錯誤檢查。嚴格模式也確保了 javascript 代碼更加的健壯,運行的也更加快速。

嚴格模式會阻止使用在將來極可能被引入的預留關鍵字。

你應該在你的腳本中啓用嚴格模式,最好是在獨立的 IIFE 中應用它。避免在你的腳本第一行使用它而致使你的全部腳本都啓動了嚴格模式,這有可能會引起一些第三方類庫的問題。

不推薦

'use strict';
(function(){
    // some code
}());

推薦

(function(){
    'use strict';
    // some code
}());

變量聲明

老是使用 var 來聲明變量。如不指定 var,變量將被隱式地聲明爲全局變量,這將對變量難以控制。若是沒有聲明,變量處於什麼定義域就變得不清(能夠是在 Document 或 Window 中,也能夠很容易地進入本地定義域)。因此,請老是使用 var來聲明變量。

採用嚴格模式帶來的好處是,當你手誤輸入錯誤的變量名時,它能夠經過報錯信息來幫助你定位錯誤出處。

不推薦

x = 10;
y = 100;

推薦

var x = 10,
    y = 100;

理解 javascript 的定義域

在 javascript 中變量和方法定義會自動提高到執行以前。javascript 只有 function 級的定義域,而無其餘不少編程語言中的塊定義域,因此使得你在某一 function 內的某語句和循環體中定義了一個變量,此變量可做用於整個 function 內,而不只僅是在此語句或循環體中,由於它們的聲明被 javascript 自動提高了。

咱們經過例子來看清楚這究竟是怎麼一回事:

原 function

(function(){
    'use strict';
    var a = 10;
    for(var i = 0; i < a; i++) {
        var b = i * i;
        console.log(b);
    }
    if(a === 10) {
        var f = function() {
            console.log(a);
        };
        f();
    }
    function x() {
        console.log('Mr. X!');
    }
    x();
}());

被 js 提高事後

(function(){
    'use strict';
    var a,
        i,
        b,
        f;
    function x() {
        console.log('Mr. X!');
    }
  
    a = 10;
    for(i = 0; i < a; i++) {
        b = i * i;
        console.log(b);
    }
    if(a === 10) {
        f = function() {
            console.log(a);
        };
        f();
    }
    x();
}());

根據以上提高過程,你是否可理解如下代碼?

有效代碼

(function(){
    'use strict';
    var a = 10;
    i = 5;
    x();
    for(var i; i < a; i++) {
        console.log(b);
        var b = i * i;
    }
    if(a === 10) {
        f = function() {
            console.log(a);
        };
        f();
        var f;
    }
    function x() {
        console.log('Mr. X!');
    }
}());

正如你所看到的這段使人充滿困惑與誤解的代碼致使了出人意料的結果。只有良好的聲明習慣,也就是下一章節咱們要提到的聲明規則,才能儘量的避免這類錯誤風險。

聲明提早

爲避免上一章節所述的變量和方法定義被自動提高形成誤解,把風險降到最低,咱們應該手動地顯示地去聲明變量與方法。也就是說,全部的變量以及方法,應當定義在 function 內的首行。

只用一個 var 關鍵字聲明,多個變量用逗號隔開。

不推薦

(function(){
    'use strict';
    
    var a = 10;
    var b = 10;
    
    for(var i = 0; i < 10; i++) {
        var c = a * b * i;
    }
    
    function f() {
    
    }
    
    var d = 100;
    var x = function() {
        return d * d;
    };
    console.log(x());
}());

推薦

(function(){
    'use strict';
    
    var a = 10,
        b = 10,
        i,
        c,
        d,
        x;
    
    function f() {
    
    }
    
    for(i = 0; i < 10; i++) {
        c = a * b * i;
    }
    
    d = 100;
    x = function() {
        return d * d;
    };
    console.log(x());
}());

把賦值儘可能寫在變量申明中。

不推薦

var a,
    b,
    c;
  
a = 10;
b = 10;
c = 100;

推薦

 

var a = 10,
    b = 10,
    c = 100;

使用匈牙利命名法

使用匈牙利命名法來命名變量。

老是使用帶類型判斷的比較判斷

老是使用 === 精確的比較操做符,避免在判斷的過程當中,由 javascript 的強制類型轉換所形成的困擾。

若是你使用 === 操做符,那比較的雙方必須是同一類型爲前提的條件下才會有效。

在只使用 == 的狀況下,javascript 所帶來的強制類型轉換使得判斷結果跟蹤變得複雜,下面的例子能夠看出這樣的結果有多怪了:

(function(){
    'use strict';
    
    console.log('0' == 0); // true
    console.log('' == false); // true
    console.log('1' == true); // true
    console.log(null == undefined); // true
    
    var x = {
        valueOf: function() {
            return 'X';
        }
    };
    
    console.log(x == 'X');
}());

明智地使用真假判斷

當咱們在一個 if 條件語句中使用變量或表達式時,會作真假判斷。if(a == true) 是不一樣於 if(a) 的。後者的判斷比較特殊,咱們稱其爲真假判斷。這種判斷會經過特殊的操做將其轉換爲 true 或 false,下列表達式通通返回 false:false0undefinednullNaN''(空字符串)。這種真假判斷在咱們只求結果而不關心過程的狀況下,很是的有幫助。

如下示例展現了真假判斷是如何工做的:

(function(){
    'use strict';
    
    function logTruthyFalsy(expr) {
        if(expr) {
            console.log('truthy');
        } else {
            console.log('falsy');
        }
    }
    
    logTruthyFalsy(true); // truthy
    logTruthyFalsy(1); // truthy
    logTruthyFalsy({}); // truthy
    logTruthyFalsy([]); // truthy
    logTruthyFalsy('0'); // truthy
    
    logTruthyFalsy(false); // falsy
    logTruthyFalsy(0); // falsy
    logTruthyFalsy(undefined); // falsy
    logTruthyFalsy(null); // falsy
    logTruthyFalsy(NaN); // falsy
    logTruthyFalsy(''); // falsy
}());

變量賦值時的邏輯操做

邏輯操做符 || 和 && 也可被用來返回布爾值。若是操做對象爲非布爾對象,那每一個表達式將會被自左向右地作真假判斷。基於此操做,最終總有一個表達式被返回回來。這在變量賦值時,是能夠用來簡化你的代碼的。

不推薦

if(!x) {
    if(!y) {
        x = 1;
    } else {
        x = y;
    }
}

推薦

x = x || y || 1;

這一小技巧常常用來給方法設定默認的參數。

(function(){
    'use strict';
    
    function multiply(a, b) {
        a = a || 1;
        b = b || 1;
        
        console.log('Result ' + a * b);
    }
    
    multiply(); // Result 1
    multiply(10); // Result 10
    multiply(3, NaN); // Result 3
    multiply(9, 5); // Result 45
}());

分號

老是使用分號,由於隱式的代碼嵌套會引起難以察覺的問題。固然咱們更要從根本上來杜絕這些問題。

javascript 中語句要以分號結束,不然它將會繼續執行下去,無論換不換行。

澄清:分號與函數

分號須要用在表達式的結尾,而並不是函數聲明的結尾。區分它們最好的例子是:

var foo = function() {
    return true;
};

function foo() {
    return true;
}

嵌套函數

嵌套函數是很是有用的,好比用在持續建立和隱藏輔助函數的任務中。你能夠很是自由隨意地使用它們。

語句塊內的函數聲明

切勿在語句塊內聲明函數,在 ECMAScript5 的嚴格模式下,這是不合法的。函數聲明應該在定義域的頂層。但在語句塊內可將函數申明轉化爲函數表達式賦值給變量。

不推薦

if (x) {
    function foo() {}
}

推薦

if (x) {
    var foo = function() {};
}

異常

基本上你沒法避免出現異常,特別是在作大型開發時(使用應用開發框架等等)。

在沒有自定義異常的狀況下,從有返回值的函數中返回錯誤信息必定很是的棘手,更別提多不優雅了。很差的解決方案包括了傳第一個引用類型來接納錯誤信息,或老是返回一個對象列表,其中包含着可能的錯誤對象。以上方式基本上是比較簡陋的異常處理方式。適時可作自定義異常處理。

在複雜的環境中,你能夠考慮拋出對象而不只僅是字符串(默認的拋出值)。

if(name === undefined) {
    throw {
        name: 'System Error',
        message: 'A name should always be specified!'
    }
}

標準特性

老是優先考慮使用標準特性。爲了最大限度地保證擴展性與兼容性,老是首選標準的特性,而不是非標準的特性(例如:首選 string.charAt(3) 而不是 string[3];首選 DOM 的操做方法來得到元素引用,而不是某一應用特定的快捷方法)。

簡易的原型繼承

若是你想在 javascript 中繼承你的對象,請遵循一個簡易的模式來建立此繼承。若是你預計你會趕上覆雜對象的繼承,那能夠考慮採用一個繼承庫,好比 Proto.js by Axel Rauschmayer

簡易繼承請用如下方式:

(function(){
    'use strict';
    
    // Constructor function
    function Apple(name) {
        this.name = name;
    }
    // Defining a method of apple
    Apple.prototype.eat = function() {
        console.log('Eating ' + this.name);
    };
    
    // Constructor function
    function GrannySmithApple() {
        // Invoking parent constructor
        Apple.prototype.constructor.call(this, 'Granny Smith');
    }
    // Set parent prototype while creating a copy with Object.create
    GrannySmithApple.prototype = Object.create(Apple.prototype);
    // Set constructor to the sub type, otherwise points to Apple
    GrannySmithApple.prototype.constructor = GrannySmithApple;
    
    // Calling a super method
    GrannySmithApple.prototype.eat = function() {
        // Be sure to apply it onto our current object with call(this)
        Apple.prototype.eat.call(this);
        
        console.log('Poor Grany Smith');
    };
    
    // Instantiation
    var apple = new Apple('Test Apple');
    var grannyApple = new GrannySmithApple();
    
    console.log(apple.name); // Test Apple
    console.log(grannyApple.name); // Granny Smith
    
    // Instance checks
    console.log(apple instanceof Apple); // true
    console.log(apple instanceof GrannySmithApple); // false
    
    console.log(grannyApple instanceof Apple); // true
    console.log(grannyApple instanceof GrannySmithApple); // true
    
    // Calling method that calls super method
    grannyApple.eat(); // Eating Granny Smith\nPoor Grany Smith
}());

使用閉包

閉包的建立也許是 js 最有用也是最易被忽略的能力了。關於閉包如何工做的合理解釋

切勿在循環中建立函數

在簡單的循環語句中加入函數是很是容易造成閉包而帶來隱患的。下面的例子就是一個典型的陷阱:

不推薦

(function(w){
    'use strict';
    
    var numbers = [1, 2, 3],
        i;
    
    for(i = 0; i < numbers.length; i++) {
        w.setTimeout(function() {
            w.alert('Index ' + i + ' with number ' + numbers[i]);
        }, 0);
    }
}(window));

接下來的改進雖然已經解決了上述例子中的問題或 bug,但仍是違反了不在循環中建立函數或閉包的原則。

不推薦

(function(w){
    'use strict';
    var numbers = [1, 2, 3],
        i;
    
    for(i = 0; i < numbers.length; i++) {
        (function(index, number){
            w.setTimeout(function() {
                w.alert('Index ' + index + ' with number ' + number);
            }, 0);
        }(i, numbers[i]));
    }
}(window));

接下來的改進已解決問題,並且也遵循了規範。但是,你會發現看上去彷佛過於複雜繁冗了,應該會有更好的解決方案吧。

不徹底推薦

(function(w){
    'use strict';
    var numbers = [1, 2, 3],
        i;
    function alertIndexWithNumber(index, number) {
        return function() {
            w.alert('Index ' + index + ' with number ' + number);
        };
    }
    for(i = 0; i < numbers.length; i++) {
        w.setTimeout(alertIndexWithNumber(i, numbers[i]), 0);
    }
}(window));

將循環語句轉換爲函數執行的方式問題能獲得立馬解決,每一次循環都會對應地建立一次閉包。函數式的風格更加值得推薦,並且看上去也更加地天然和可預料。

推薦

(function(w){
    'use strict';
    var numbers = [1, 2, 3];
    
    numbers.forEach(function(number, index) {
        w.setTimeout(function() {
            w.alert('Index ' + index + ' with number ' + number);
        }, 0);
    });
}(window));

eval 函數(魔鬼)

eval() 不但混淆語境還很危險,總會有比這更好、更清晰、更安全的另外一種方案來寫你的代碼,所以儘可能不要使用 eval()函數。

this 關鍵字

只在對象構造器、方法和在設定的閉包中使用 this 關鍵字。this 的語義在此有些誤導。它時而指向全局對象(大多數時),時而指向調用者的定義域(在 eval 中),時而指向 DOM 樹中的某一節點(當用事件處理綁定到 html 屬性上時),時而指向一個新建立的對象(在構造器中),還時而指向其它的一些對象(若是函數被 call() 和 apply() 執行和調用時)。

正由於它是如此容易地被搞錯,請限制它的使用場景:

  • 在構造函數中
  • 在對象的方法中(包括由此建立出的閉包內)

使用 ECMAScript5

建議使用 ECMAScript5 中新增的語法和函數。這將簡化你的程序,並讓你的代碼更加靈活和可複用。

數組和對象的屬性迭代

用 ECMAScript5 的迭代方法來迭代數組。使用 Array.forEach 或者若是你要在特殊場合下中斷迭代,那就用 Array.every

(function(){
    'use strict';
    
    [1, 2, 3, 4, 5].every(function(element, index, arr) {
        console.log(element + ' at index ' + index + ' in array ' + arr);
        
        if(index !== 5) {
            return true;
        }
    });
    
    var obj = {
        a: 'A',
        b: 'B',
        'c-d-e': 'CDE'
    };
    
    Object.keys(obj).forEach(function(element, index, arr) {
        console.log('Key ' + element + ' has value ' + obj[element]);
    });
}());

數組和對象字面量

用數組和對象字面量來代替數組和對象構造器。數組構造器很容易讓人在它的參數上犯錯。

不推薦

var a1 = new Array(x1, x2, x3);
var a2 = new Array(x1, x2);
var a3 = new Array(x1);
var a4 = new Array();

正因如此,若是將代碼傳參從兩個變爲一個,那數組頗有可能發生意料不到的長度變化。爲避免此類怪異情況,請老是採用更多可讀的數組字面量。

推薦

var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];

對象構造器不會有相似的問題,可是爲了可讀性和統一性,咱們應該使用對象字面量。

不推薦

var o = new Object();
var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;

推薦

var o = {};
var o2 = {
    a: 0,
    b: 1,
    c: 2,
    'strange key': 3
};

修改內建對象的原型鏈

修改內建的諸如 Object.prototype 和 Array.prototype 是被嚴厲禁止的。修改其它的內建對象好比 Function.prototype,雖危害沒那麼大,但始終仍是會致使在開發過程當中難以 debug 的問題,應當也要避免。

自定義 toString() 方法

你能夠經過自定義 toString() 來控制對象字符串化。這很好,但你必須保證你的方法老是成功並不會有其它反作用。若是你的方法達不到這樣的標準,那將會引起嚴重的問題。若是 toString() 調用了一個方法,這個方法作了一個斷言,當斷言失敗,它可能會輸出它所在對象的名稱,固然對象也須要調用 toString()

圓括號

通常在語法和語義上真正須要時才謹慎地使用圓括號。不要用在一元操做符上,例如 deletetypeof 和 void,或在關鍵字以後,例如 returnthrowcasenew 等。

字符串

統一使用單引號(‘’),不使用雙引號(「」)。這在建立 html 字符串很是有好處:

var msg = 'This is some HTML <div class="makes-sense"></div>';

三元條件判斷(if 的快捷方法)

用三元操做符分配或返回語句。在比較簡單的狀況下使用,避免在複雜的狀況下使用。沒人願意用 10 行三元操做符把本身的腦子繞暈。

不推薦

if(x === 10) {
    return 'valid';
} else {
    return 'invalid';
}

推薦

return x === 10 ? 'valid' : 'invalid';

參考文獻

相關文章
相關標籤/搜索