JavaScript學習筆記(二十一) 對象創建模式-命名空間模式

在JavaScript中創建對象是非常簡單的,你可以使用對象字面量(object literal)或者使用構造函數(方法)(constructor)。

在這章裏,我們會超出這些,看到一些額外的對象創建模式。

JavaScript語言是簡單而直接的,通常沒有特別的語法實現你在其它語言中使用的功能,比如命名空間(namespaces)、模塊(modules)、包(packages),私有屬性(private properties)和靜態變量(static members)。接下來,我們會去實現這些常見的模式,或者使用其他方式代替,或者比較二者的不同。

我們會看到命名空間(namespacing),依賴聲明(dependency declaration),模塊模式(module pattern)和沙箱模式(sandbox patterns),這些會幫助組織和構建你的程序代碼並且減少潛在的全局變量。討論的主題還包括private或者享有特權(privileged)的成員變量,static或者private static的成員,對象常量,鏈式編程,使用特殊的方法的構造函數。

命名空間模式(Namespace Pattern)

命名空間會幫助減少程序需要的全局變量的數量,同時也幫助我們防止命名衝突或者過多的命名前綴(加前綴爲了防止命名衝突)。
JavaScript語法本身沒有內置的命名空間語法,但這個功能是十分容易實現的。爲了防止大量的函數,對象或者其它變量污染全局作用域,你可以爲你的程序或類庫創建一個(理想情況只有一個)全局對象。然後你可以給這個對象添加所有的功能性需求。
像下面這個例子:
// BEFORE: 5 globals
// Warning: antipattern
// constructors
function Parent() {}
function Child() {}
// a variable
var some_var = 1;
// some objects
var module1 = {};
module1.data = {a: 1, b: 2};
var module2 = {};
你可以重構這種類型的代碼通過爲你的程序創建一個全局對象,比如叫做:MYAPP,並且將你所有的函數和變量作爲這個全局變量的屬性:
// AFTER: 1 global
// global object
var MYAPP = {};
// constructors
MYAPP.Parent = function () {};
MYAPP.Child = function () {};
// a variable
MYAPP.some_var = 1;
// an object container
MYAPP.modules = {};
// nested objects
MYAPP.modules.module1 = {};
MYAPP.modules.module1.data = {a: 1, b: 2};
MYAPP.modules.module2 = {};
對於全局命名空間對象的名字,你可以選擇你的程序或者類庫的名字,你的域名,或者公司的名字。通常程序猿都會遵循約定,全局變量名全大寫,所以更容易被代碼的讀者發現。(但也要記住全部大寫通常也被用來命名常量)
這種模式是給你的代碼添加命名空間和防止命名衝突的好方法,不僅僅可以防止你自己代碼中的命名衝突,也可以防止你的代碼和第三方代碼命名衝突,比如JavaScript類庫或控件。
這種模式被極力推薦並且可以完美適用於很多任務,但它也有一些不足之處:
  • 需要更多的輸入;每個變量和函數都要添加一個前綴,會增加需要下載的代碼量。
  • 只有一個全局變量意味着任何地方的代碼都可以修改這個全局變量,其它地方都會受到影響。
  • 很長的嵌套名稱會降低屬性查找速度。
後面說到沙箱模式會解決這些不足。

通用的命名空間函數(General Purpose Namespace Function)

隨着程序複雜度的增加,有部分代碼會被分開放到不同的文件中,然後有條件的被包含(included),假設你的代碼是第一個聲明某個命名空間或者它的屬性是不安全的;
有些你正在添加的屬性可能已經存在了,並且你可能覆蓋了它們。
因此,在添加一個屬性或者創建一個命名空間之前,最好先檢查一下它是否已經存在,就像下面這個例子一樣:
// unsafe
var MYAPP = {};
// better
if (typeof MYAPP === "undefined") {
    var MYAPP = {};
}
// or shorter
var MYAPP = MYAPP || {};
你可以看到這些添加的檢查會很快導致大量重複代碼。比如:如何你想定義MYAPP.modules.module2,你講不得不進行三次檢查,每個你定義的屬性或對象都需要一次;
這就是爲什麼你需要一個方便的可重用的函數來具體維護命名空間。讓我們將這個函數叫做namespace() 並且向這樣去使用:
// using a namespace function
MYAPP.namespace('MYAPP.modules.module2');
// equivalent to:
// var MYAPP = {
//     modules: {
//         module2: {}
//     }
// };
接下來的例子是命名空間函數的實現。這個實現是非破壞性的,意味着如何一個命名空間已經存在,那麼它不會被重新創建:
var MYAPP = MYAPP || {};
MYAPP.namespace = function (ns_string) {
    var parts = ns_string.split('.'),
        parent = MYAPP,
        i;
    // strip redundant leading global
    if (parts[0] === "MYAPP") {
        parts = parts.slice(1);
    }
    for (i = 0; i < parts.length; i += 1) {
        // create a property if it doesn't exist
        if (typeof parent[parts[i]] === "undefined") {
            parent[parts[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
};
這個實現可以這樣使用:
// assign returned value to a local var
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // true
// skip initial `MYAPP`
MYAPP.namespace('modules.module51');
// long namespace
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章