名稱管理



JavaScript本身沒有名稱空間管理的機制,名稱都是物件上的特性,要不就成為全域物件上的特性(全域變數),要不就使用var宣告使之成為呼叫物件上的變數(區域變數)。

名稱衝突的問題極容易在JavaScript中發生,就算是在同一個.js檔案中也有可能發生。例如你也許寫了個validate()函式,假以時日別人接手你的程式,然後在檔案中某處又定義了另一個validate()函式:
function validate() {
    //.. 作些驗證
}
// 很長很長的程式。。
// 某年某月的某一天。。
function validate() {
    //.. 作些別的驗證
}

很不幸地,之後的validate()函式定義會覆蓋前一個函式,也許會讓之前可以動作的功能失效。

最基本的名稱空間管理,就是將這個函式作為某物件的特性,該物件是對組織或單位有意義的名稱所參考。例如:
var openhome = {};     // 作為名稱空間
function validate() {
    //.. 作些驗證
}
openhome.validate = validate;

想要取用你定義的validate()函式,則可以如下:
openhome.validate();

其它人在定義函式時,也可以作類似考量。例如他也許在同一個.js中如下定義:
var caterpillar = {};     // 作為名稱空間
function validate() {
    //.. 作些驗證
}
caterpillar.validate = validate;

呼叫時就使用:
caterpillar.validate();

如此就不會發生名稱覆蓋的問題。或許在同一個.js中,以上名稱衝突的情況比較算少數,通常這會發生在兩個.js分別由兩個不同組識或作者撰寫時。

如果同一個組識在撰寫程式庫時,會使用同一名稱作為名稱空間,如果你在a.js中如下撰寫:
var openhome = {};     // 作為名稱空間
function validate() {
    //.. 作些驗證
}
openhome.validate = validate;

另一個人在b.js中如下撰寫:
var openhome = {};     // 作為名稱空間
function format() {
    //.. 作些格式化
}
openhome.format = format;

那在同一個網頁中引用a.js與b.js會如何?如果b.js較後引用,那麼,就沒辦法使用openhome.validate()了。在建立名稱空間時,可以先測試一下作為名稱空間的物件是否存在,如果存在就直接使用,不存在再建立新的物件作為名稱空間。例如a.js可如下:
var openhome = openhome || {};
function validate() {
    //.. 作些驗證
}
openhome.validate = validate;

如上,只有在openhome為undefined時,才會建立新的物件,b.js也作類似撰寫,就不會發生上述的情況。

在撰寫JavaScript時,要儘量避免使用全域變數,上面的例子似乎沒有?有的!a.js中至少侵入了兩個,一個是openhome名稱,一個是validate(),要記得,在全域範圍定義函式時,函式名稱就是全域物件上的特性。你可以這麼解決:
var openhome = openhome || {};
openhome.validate = function() {
    // 作些驗證
};

但如果這個函式也想作為其它物件上的特性,函式實字的作法會比較不方便。有個方式可以比較漂亮地解決。例如:
var openhome = openhome || {};
(function() {
    function validate() {
        // 作些驗證
    }
    openhome.validate = validate;
    // ... 其它...
})();

乍看有些複雜,事實上,首先是撰寫..
function() {
}

這寫下了一個函式實字,沒有名稱參考至它,所以不會污染全域名稱空間...接著...
(function() {
})

加上括號表示優先運算子,將括號中的函式實字運算為一個函式物件,接著...
(function() {
})();

最後的括號表示呼叫傳回的函式物件。在這個函式物件中所建立的函式是區域的....
var openhome = openhome || {};
(function() {
    function validate() { // validate 是區域的
        // 作些驗證
    }
    openhome.validate = validate;
    // ... 其它...
})();

所以不會污染全域名稱空間,到最後,以上只會有openhome這個名稱在全域中。像這樣的模式廣泛運用在JavaScript的設計領域,通常會用在一個.js中為模組初始化。

在設計程式庫時,若有名稱想避免別人佔用干擾,則有個慣用手法。例如,若不想被別人佔用干擾global這個名稱,則可以如下:
(function(global) {
   var global.openhome = global.openhome || {};
    ...

})(this);

也許你的匿名函式 初始中,不確定會用在哪個環境中,因此想使用global這個名稱作為全域變數,一方面也怕別人干擾global這個名稱,使用以上手法,若是在瀏覽器 中,真正的全域名稱this所參考的物件會傳入函式作為參數global參考的對象,而global為區域變數,所以不受外部其它程式庫所定義的外部 變數干擾。

檔案名稱也是會發生衝突的,檔案名稱上也可以放上與組織相關的前置名稱。例如openhome.formutil.js、caterpillar.formutil.js這樣的名稱,從而避免檔案名稱上的衝突。

如果作為名稱空間的物件階層很多。例如:
openhome.book.web.some = 'some';
openhome.book.web.other = 'other';
openhome.book.web.another = 'another';

這樣不僅撰寫麻煩,而且 . 運算子的解析也會有效能影響,JavaScript很難對這種 . 運算子解析作最佳化(編譯式語言如Java通常就會最佳化)。雖然你可以使用with
with(openhome.book.web) {
    some = 'some';
    other = 'other';
    another = 'another';
}

但不建議,雖然省去一些打字上的麻煩,但with很難最佳化,甚至有可能更慢。比較好的方式是:
var web = openhome.book.web;
web.some = 'some';
web.other = 'other';
web.another = 'another';