你不知道的JavaScript--Item5 全域性變數

itKingOne發表於2018-06-21

1、儘量少用全域性物件

全域性變數的問題在於,你的JavaScript應用程式和web頁面上的所有程式碼都共享了這些全域性變數,他們住在同一個全域性名稱空間,所以當程式的兩個不同部分定義同名但不同作用的全域性變數的時候,命名衝突在所難免。

web頁面包含不是該頁面開發者所寫的程式碼也是比較常見的,例如:

  1. 第三方的JavaScript庫
  2. 廣告方的指令碼程式碼
  3. 第三方使用者跟蹤和分析指令碼程式碼
  4. 不同型別的小元件,標誌和按鈕

比方說,該第三方指令碼定義了一個全域性變數,叫做result;接著,在你的函式中也定義一個名為result的全域性變數。其結果就是後面的變數覆蓋前面的,第三方指令碼就一下子嗝屁啦!

因為,你不小心,在程式碼的某一處修改了全域性變數, 會導致依賴全域性變數的其它模組出錯。而且出錯原因難除錯,難找到。

再者就是,網頁執行肯定用到window物件,瀏覽器引擎又要遍歷一次window的屬性,效能下降。
  • 全域性變數是維繫不同模組之間的紐帶,模組之間只能通過全域性變數來訪問對方提供的功能

  • 能使用區域性變數的時候,絕不要使用全域性變數

var i,n,sum//globals
function averageScore(players){
    sum =0;
    for(i = 1, i = player.length; i<n; i++){
        sum += score(players[i]);
    }
    return sum/n;
}

保持這些變數為區域性變數,僅將其作為需要使用它們的程式碼的一部分。

function averageScore(players){
var i,n,sum;
    sum =0;
    for(i = 1, i = player.length; i<n; i++){
        sum += score(players[i]);
    }
    return sum/n;
}
  • 在browser中,this關鍵字會指向全域性的window物件

JavaScript 的全域性名稱空間也被暴露為在程式全域性作用域中可以訪問的全域性物件,該物件作為 this 關鍵字的初始值。在 Web 瀏覽器中,全域性物件被繫結到全域性的 window 變數。新增或修改全域性變數會自動更新全域性物件。

this.foo;  //undefined
foo ="global foo";  //"global foo"
this.foo;  //"global foo"

類似地,更新全域性物件也會自動地更新全域性名稱空間:

var foo ="global foo";
this.foo;  //"global foo"
this.foo ="changed";
foo;  //"changed"
  • 兩種用來改變全域性物件的方式,通過var關鍵字宣告以及給全域性物件設定屬性(通過this關鍵字)

  • 通過全域性物件進行鍼對當前執行環境的特性檢測(Feature Detection),比如在ES5中提供了一個JSON物件用來操作JSON資料,那麼可以通過if(this.JSON)來判斷當前執行環境是否支援JSON

if(!this.JSON){
    this.JSON ={
        parse:...,
        stringify:...
    }
}

2、如何避免全域性變數

方法一:只建立一個全域性變數。

MYAPP.stooge = {
    "first-name": "Joe",
    "last-name": "Howard"
};

MYAPP.flight = {
    airline: "Oceanic",
    number: 815,
    departure: {
        IATA: "SYD",
        time: "2004-09-22 14:55",
        city: "Sydney"
    },
    arrival: {
        IATA: "LAX",
        time: "2004-09-23 10:42",
        city: "Los Angeles"
    }
};

方法二:使用模組模式

var serial_maker = function (  ) {

    var prefix = '';
    var seq = 0;
    return {
        set_prefix: function (p) {
            prefix = String(p);
        },
        set_seq: function (s) {
            seq = s;
        },        get_prefix: function (s) {            return prefix;        },
        gensym: function () {
            var result = prefix + seq;
            seq += 1;
            return result;
        }
    };
};

var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(100);
var unique = seqer.gensym();    // "Q1000"var getPrefix= seqer.get_prefix();    // "Q"

所謂模組模式,就是建立一個函式,該函式包括,私有變數和一個特權物件,特權物件的內容是,利用閉包能訪問到私有變數的函式,最後返回特權物件。

首先,方法二,不僅可以當作全域性變數用,也可以用在區域性宣告全域性變數。因為就算你在不知道某個地方修改了seqer,就會立即報錯,因為這是個物件,不是字串。

方法三:零全域性變數

零全域性變數實際上是為了適應一小段封閉程式碼而採取的一種區域性變數處理方式,只適合在一些特殊場景中使用。最常見的就是一些不會被其他指令碼訪問到的完全獨立的指令碼。 
使用零全域性變數的方式需要採用立即執行函式,用法如下。

( function ( win ) {

    'use strict' ;
    var doc = win.document ;
    //在此定義其他的變數並書寫程式碼
} )

3、意外的全域性變數

由於JavaScript的兩個特徵,不自覺地建立出全域性變數是出乎意料的容易。首先,你可以甚至不需要宣告就可以使用變數;第二,JavaScript有隱含的全域性概念,意味著你不宣告的任何變數都會成為一個全域性物件屬性。參考下面的程式碼:

function sum(x, y) {
   // 不推薦寫法: 隱式全域性變數
   result = x + y;
   return result;
}

此段程式碼中的result沒有宣告。程式碼照樣運作正常,但在呼叫函式後你最後的結果就多一個全域性名稱空間,這可以是一個問題的根源。

經驗法則是始終使用var宣告變數,正如改進版的sum()函式所演示的:

function sum(x, y) {
   var result = x + y;
   return result;
}

另一個建立隱式全域性變數的反例就是使用任務鏈進行部分var宣告。下面的片段中,a是本地變數但是b確實全域性變數,這可能不是你希望發生的:

// 反例,勿使用
function foo() {
   var a = b = 0;
   // ...
}

此現象發生的原因在於這個從右到左的賦值,首先,是賦值表示式b = 0,此情況下b是未宣告的。這個表示式的返回值是0,然後這個0就分配給了通過var定義的這個區域性變數a。換句話說,就好比你輸入了:

var a = (b = 0); 
如果你已經準備好宣告變數,使用鏈分配是比較好的做法,不會產生任何意料之外的全域性變數,如:

function foo() {
   var a, b;
   // ... a = b = 0; // 兩個均區域性變數
}

然而,另外一個避免全域性變數的原因是可移植性。如果你想你的程式碼在不同的環境下(主機下)執行,使用全域性變數如履薄冰,因為你會無意中覆蓋你最初環境下不存在的主機物件

總是記得通過var關鍵字來宣告區域性變數

使用lint工具來確保沒有隱式宣告的全域性變數

相關文章