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