JavaScript執(zhí)行環(huán)境在很多方面都有其獨特之處。全局變量和函數(shù)的使用便是其中之一。事實上,JavaScript的初始執(zhí)行環(huán)境是由多種多樣的全局變量所定義的,這些全局變量在腳本環(huán)境創(chuàng)建之初就已經(jīng)存在了。這些都是掛載在全局對象上的。
在瀏覽器中,window對象往往重載并等同于全局對象,因此任何在全局作用域中聲明的變量和函數(shù)都是window對象的屬性,如下所示,兩者都是window對象的屬性。
var color = 'red';
function showColor () {
alert(color);
}
console.log(window.color); // 'red'
console.log(typeof window.showColor); // 'function'
1. 全局變量帶來的問題
一般來講,創(chuàng)建全局變量被認為是糟糕的事,尤其是在團隊開發(fā)的大背景下更是如此。
命名沖突: 當腳本中的全局變量和全局函數(shù)越來越多時,發(fā)生命名沖突的概率也大為增加。如上代碼所示,當全局變量color和全局函數(shù)showColor()在同一個文件時還好,但是當有很多地方引用全局變量和全局對象時,追蹤起來就變得相當麻煩。
代碼脆弱: 一個依賴于全局變量的函數(shù)即是深耦合于上下文環(huán)境之中,如果環(huán)境發(fā)生改變,函數(shù)很可能就失效了。
意外的全局變量: 當你給一個未被var語句聲明過的變量賦值時,JavaScript就會自動創(chuàng)建一個全局變量,尤其當你無意中創(chuàng)建的全局變量與系統(tǒng)定義的全局變量相同時,會修改系統(tǒng)的全局變量的值。如下:
function doSomething () {
var count = 10;
title = "Maintainable JavaScript"; // 不好的寫法,創(chuàng)建了全局變量
}
2. 解決全局變量帶來的問題
a. 避免意外的全局變量
避免意外的全局變量可以使用JSLint和JSHint等檢測工具來,或者使用JS的嚴格模式,使用JS的嚴格模式會通知JavaScript引擎在運行代碼前執(zhí)行更嚴格的錯誤處理和語法檢查。其中一個規(guī)則可以探測未聲明變量的賦值操作。
b. 單全局變量
單全局變量的含義即只創(chuàng)建一個全局變量。如JQuery定義了兩個特定的全局變量,$和jQuery,只有在$被其他類庫使用了的情況下,為了避免沖突,應當使用jQuery。
“單全局變量” 的意思是所創(chuàng)建的這個唯一全局對象名是獨一無二的,并將你所有的功能代碼都掛載到這個全局對象上。因此每個可能的全局變量都成為你唯一全局對象的屬性,從而不會創(chuàng)建多個全局變量。
c. 命名空間
即使你的代碼只有一個全局對象,也存在著全局污染的可能性。大多數(shù)使用單全局變量模式的項目同樣包含“命名空間”的概念。命名空間是簡單的通過全局對象的單一屬性表示的功能分組。將功能按照命名空間進行分組,可以讓你的全局對象變得井然有序,同時讓團隊成員能夠知曉新功能應該屬于哪個部分。
現(xiàn)有js中并不支持原生的命名空間。在JS中創(chuàng)建的任何對象都默認是全局對象。在現(xiàn)代的大規(guī)模JS開發(fā)中,不采用命名空間會造成非常糟糕的命名方式,導致代碼丑陋不可讀。當引入第三方庫后,更可能會發(fā)生明明覆蓋的情況。
好消失是:在ES6中,就有了native的命名空間可以用了,但是當下我們還需要一些特殊的手段來模擬命名空間的概念。簡單來說,就是創(chuàng)建一個簡單字面量來打包所有的相關函數(shù)和變量。這個簡單的對象字面量模擬了命名空間的作用。
var NAMESPACE = {
person: funnction(name) {
this.name = name;
this.getName = function() {
return this.name;
}
}
}
// 調(diào)用方法
var p = new NAMESPACE.person("andy");
p.getName()
如此一來,我們就可以通過命名空間來聲明多個person對象了。但是這里還有一個問題,我們這里使用的是一個全局對象,在添加這個“命名空間”的時候,我們有可能覆蓋全局空間中的同名對象。因此我們需要再聲明命名空間之前進行檢查,保證全局空間的安全:
// 在聲明之前進行檢查,防止覆蓋全局的同名對象。
var NAMESPACE = NAMESPACE || {};
若全局空間中已經(jīng)有同名對象,則不覆蓋該對象;否則創(chuàng)建一個新的命名空間。采用了這個安全地命名空間后,聲明的方法也需要略作改動:
var NAMESPACE = NAMESPACE || {};
MYNAMESPACE.person = function(name) {
this.name = name;
};
MYNAMESPACE.person.prototype.getName = function() {
return this.name;
};
// 使用方法
var p = new MYNAMESPACE.person("ifcode");
p.getName(); // ifcode
注意在定義命名空間構造函數(shù)時,需要將其定義在prototype上,否則新建的實例無法訪問對象的方法。
d. 模塊化
另外一種基于單全局變量的擴充方法是模塊,模塊是一種通用的功能片段,它并沒有創(chuàng)建新的全局變量或命名空間。相反,這些代碼都存放于一個表示執(zhí)行一個任務或發(fā)布一個借口的單函數(shù)中??梢杂靡粋€名稱來表示這個模塊,同樣這個模塊可以依賴其他模塊。
e. 零全局變量
這個種方法應用場景不多,只有在特殊場景下才會應用。最常見的情形是一段不會被其他腳本訪問到的完全獨立的腳本,之所以存在這種情形,是因為所有所需的腳本都會合并到一個文件,或者因為這段非常短小且不提供任何借口的代碼會被插入至一個頁面中,最常見的用法是創(chuàng)建一個書簽。
書簽是獨立的,他們并不知道頁面中包含什么且不需要頁面知道它的存在。最終我們需要一段“零全局變量”的腳本嵌入到頁面中,實現(xiàn)方法就是使用一個立即執(zhí)行的函數(shù)調(diào)用并將所有腳本放置其中,比如“
(function (win)) {
var doc = win.document;
// 在這里定義其他的變量
// 其他相關代碼
}
這段代碼注入到頁面中不會產(chǎn)生任何的全局變量,之后你可以通過將函數(shù)設置為嚴格模式來避免創(chuàng)建全局變量。