跳到主要內容
黯羽輕揚每天積累一點點

模塊模式_JavaScript 設計模式 2

免費2015-07-12#JS#Design_Pattern#模块模式#Module_Pattern#揭示模块模式#Revealing_Pattern.高级模块模式

所謂 Module(模塊)模式,其實是一種針對 JS 的封裝思想,很多語言提供了訪問控制機制(public, private 等等),但 JS 沒有,所以才有了模塊模式

寫在前面

其實此處的模塊模式就是道格拉斯提出的 Module 模式,完全一樣,但作者對主流 JS 庫(框架)的模塊管理做了分析,算是亮點。

一.模塊模式

JS 沒有 class 的概念,自然也就沒有了基於類的封裝一說,class 最大的優點可能就是模塊(命名空間)管理與訪問控制,這些 JS 都沒有。但利用 JS 函數作用域的特點可實現訪問控制(封裝),會說話的代碼如下:

var module = (function() {
    // 私有屬性
    var attr1 = 1;
    function fun1() {
        // ...
    }

    // 返回匿名對象,當然,也可以返回不匿名的對象
    return {
        // 公有屬性
        attr2: attr1,
        fun2: fun1
    }
})();   // IIFE, 匿名函數立即執行

從外部只能訪問公有屬性 attr2fun2,私有屬性 attr1fun1 是被限制訪問的(undefined),這樣看來算是封裝良好了。但問題是 module 是一個單例,無法復用,如果想要復用,還需要做一點小變動:

function Module() {
    // 私有屬性
    var attr1 = 1;
    function fun1() {
        // ...
    }

    // 公有屬性
    this.attr2 = attr1;
    this.fun2 = fun1;
}
var module1 = new Module();
var module2 = new Module();

從訪問控制(封裝)的角度來看,兩種方式都是可以的,第一種方式的缺點是單例的限制,還存在初始化無法傳參等問題,第二種方式支持復用,同時也存在多個實例的開銷,具體使用應根據場景來定。

P.S.關於道格拉斯的 Module 模式的更多信息,請查看 黯羽輕揚:JS 學習筆記 3_函數表達式

二.Module 模式變化

1.引入混入

(function(m, arg){
    // ...
})(Module, arg);

這其實和模塊模式沒什麼關係,重點也不是作者強調的「別名」,而是「模塊文件化」中的一種重要方法,AMD、CMD 中都是單模塊對應單文件,這種方式提供了一種擴充位於其他文件中的模塊的方法。(「引入混入」?就當沒看見好了。。)

上面的方式存在一點問題,就是要求模塊必須嚴格按順序加載(必須先加載 Module 對象定義),可以用下面這種方式解除限制:

(function(m, arg){
    // ...
})(Module || {}, arg);

用這種方式可以實現模塊異步加載,但依賴其它模塊的模塊肯定是不適用的。

2.引出

(function() {
    // 創建
    var obj = {};
    // 增強
    obj.attr = 1;
    // 返回
    return obj;
})();

沒什麼特別的地方,值得一提的是道格拉斯的模塊模式思想:創建 -> 增強 -> 返回,至於「引出」,我也不知道作者想說什麼。。

三.JS 庫(框架)的模塊管理

1.Dojo 模塊

obj.setObject('module.submod.submod', (function() {
    // 私有屬性
    // var
    // function

    return {
        // 公有屬性
        // attr: val
    };
})();

很特別的模塊定義方式,setObject(str, obj),其實還可以有更方便的方式:

String.prototype.ns = function (obj) {
    return setObject(this.toString(), obj);
};

有些時候充分利用 JS 提供的靈活性將非常方便(不用過分擔心擴展原型帶來的問題,ns 可能衝突,那換成 regns、regNS 呢。。)

2.ExtJS 模塊

Ext.namespace('module');

module.submod = (function() {
    // 私有屬性
    // var
    // function

    return {
        // 公有屬性
        // attr: val
    };
})();

和基本的模塊模式很像,很容易實現。

3.YUI 模塊

Y.namespace('module.submod') = (function() {
    // 私有屬性
    // var
    // function

    return {
        // 公有屬性
        // attr: val
    };
})();

實現方式都差不多,這樣實現支持鏈式調用:Y.namespace('module.submod').attr = val;

4.JQuery 模塊

function regLib(mod) {
    $(function() {
        if (mod.init) {
            mod.init();
        }
    });

    return mod;
}
var module = regLib((function() {
    // 私有屬性
    // var
    // function

    return {
        // 公有屬性
        // attr: val
        init: function() {
            // ...
        }
    };
})());

利用 regLib 函數來實現加載新模塊時自動把 mod.init() 放在 DOMready 事件處理器中,也算不上特色。其實 JQuery 提供的插件機制就是模塊管理,此處沒有使用插件機制,但提供了一種 JQuery 中以非插件方式管理自定義模塊的方法。JQuery 插件機制如下:

;(function ($) {
    // 私有屬性
    // var
    // function

    $.fn.myPlugin = {
        // 公有屬性
        // attr: val
        version: 1.0
    };
})(jQuery);

沒什麼本質的區別,都是套路。

四.Revealing Module(揭示模塊)模式

與前面提到的模塊模式的唯一區別是要求公有屬性是私有屬性的引用,其實第一例代碼已經這樣做了。僅僅因為這一點就把它作為一個新模式可能有些不值得,所以這部分內容就擠在這裡了。

模塊內部可以非常靈活的處理,可以選擇直接公開匿名函數的引用,私有屬性的引用,或者是基本值。沒必要為了滿足什麼「揭示模塊模式」,而放棄這種靈活性。

五.高級模塊模式

P.S.這部分內容是筆者補充的,幸虧書上內容不多,才讓筆者有機會找到這篇文章:深入理解 JavaScript 模塊模式

1.淺拷貝

上面提到的所有實現方式都是直接擴展模塊對象的,如果想要在原模塊對象基礎上創造一個獨立的模塊,就需要淺/深拷貝了,比如:

var MODULE_TWO = (function (old) {
    var my = {},
        key;
     
    for (key in old) {
        if (old.hasOwnProperty(key)) {
            my[key] = old[key];
        }
    }
     
    var super_moduleMethod = old.moduleMethod;
    my.moduleMethod = function () {
        // override method on the clone, access to super through super_moduleMethod
    };
     
    return my;
}(MODULE));

上面的示例是淺拷貝,引用類型的屬性還是共享的,想要徹底獨立出來,必須進行深拷貝。

2.跨文件私有狀態

這是一種很特別的方法,類似於 [黯羽輕揚:《JavaScript 語言精粹》之函數化](/articles/《javascript 語言精粹》之函數化/) 中提到的「函數化的思想」,在此基礎上增加了訪問控制。示例代碼如下:

var MODULE = (function (my) {
    var _private = my._private = my._private || {},

        // 模塊加載前,開啟對_private 的訪問,以實現擴充部分對私有內容的操作
        _unseal = my._unseal = my._unseal || function () {
            my._private = _private;
            my._seal = _seal;
            my._unseal = _unseal;
        },

        // 模塊加載後,調用以移除對_private 的訪問權限
        _seal = my._seal = my._seal || function () {
            delete my._private;
            delete my._seal;
            delete my._unseal;
        };
     
    // permanent access to _private, _seal, and _unseal
     
    return my;
}(MODULE || {}));

一個模塊被分成幾個文件時,在模塊加載前調用 _unseal 獲取對私有屬性的訪問權限,模塊加載完成後調用 _seal 刪掉訪問權限,而 _private 就是《JavaScript 語言精粹》函數化部分中的 my 容器。

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論