1. 程式人生 > >js模塊化編程思想、實現與規範

js模塊化編程思想、實現與規範

可讀性 另一個 一起 ssi async asc requirejs 有一個 依賴問題

隨著BS架構的發展,網站逐漸變成了互聯網應用程序,嵌入網絡的JavaScript代碼越來越龐大,越來越復雜(業務邏輯處理或用戶交互很多寫在前端)。網頁越來越像桌面程序,需要一個團隊分工協作、進度管理、單元測試等。。開發者不得不使用軟件工程的方法,管理網頁的業務邏輯。因此JavaScript模塊化編程已經成了一個迫切的需求,理想的情況下是開發者只需要實現核心的業務邏輯,其他業務處理都可以加載別人已經寫好的模塊,做到明確分工而不會相互影響。

但是,JavaScript卻不是一種模塊化編程語言,它不支持類(class),更別說模塊(module)了。雖然ECMAScipt正在謀劃支持和推廣類和模塊的概念,但要實際投入生產還是遙遙無期,只能自己另外想辦法。為此JavaSript社區做了很多努力,努力在現有的運行環境中,利用現有的資源,實現模塊化的效果。

模塊化的原始寫法

模塊的定義就是實現特定功能的一組方法,只要把不同的函數(以及記錄狀態的變量)簡單地放在一起,就算是一個模塊了。

function f1() {
    // doSomething
}

function f2() {
    // doSomething
}

上面的函數f1()和f2()共同組成了一個模塊,使用的時候直接通過函數名調用就行了。這種做法的缺點很明顯,既汙染了全局變量(f1和f2處在全局的上下文棧中,屬於window的屬性),也無法保證不與其他模塊發生變量名沖突,而且模塊成員之間看不出直接的關系。

模塊化的對象寫法

為了解決上面的缺點,可以把模塊寫成一個對象,所有的模塊成員都放到這個對象裏面。

var module1 = {
    status: 233,
    
    f1: function() {
        // doSomething
    },
    
    f2: function() {
        // doSomething
    }
}

上面的函數f1()和函數f2()都封裝在了module1對象裏,使用的時候就是通過訪問module1對象的屬性。

module1.f1();

但是,這樣的寫法會暴露所有的模塊成員,且內部的狀態可以被外部改寫。

module1.status = 666;

模塊化的立即執行函數寫法

使用立即執行函數(Immediately-Invoked Function Expression,IIFE),可以達到不暴露私有成員的目的。

var module1 = (function() {
    var status = 233;
    
    var f1 = function() {
        // doSomething
    };

    var f2 = function() {
        // doSomething
    };
})();

使用這樣的寫法,外部的代碼就無法讀取到內部的變量。

console.log(module1.status); // undefined

這種寫法,就是JavaScript模塊化的基本寫法,後面的實現基本上都是依照這個思路。

模塊化的放大模式

如果一個模塊很大,就會要拆分成幾個小的模塊,或者一個模塊需要繼承另一個模塊,這個時候就要采用放大模式(Augmentation)。

var module1 = (function(mod) {
    mod.f3 = function() {
        // doSomething
    };

    return mod;
})(module1);

這裏為module1模塊添加了一個新函數f3(),然後返回新的module1模塊。也就是把舊的對象傳進來,給這個對象添加屬性,然後返回添加了屬性後的對象,相當於擴展。

模塊化的寬放大模式

在瀏覽器環境中,模塊的各個部分通常都是從網上獲取的,有時無法知道哪個部分會先加載。如果采用上一節的寫法,第一個執行的部分有可能加載一個不存在的空對象,這時就要采用寬放大模式(Loose Augmentation)。

var module1 = (function(mod) {
    // doSomething

    return mod;
})(window.module1 || {});

與放大模式相比,寬放大模式就是立即執行函數的參數可以是空對象。

模塊化的全局變量輸入

獨立性是模塊化的重要特點,模塊內部最好不與程序的其他部分直接交互。

為了在模塊內部調用全局變量,必須顯式地將其他變量輸入模塊。

var module1 = (function($, YAHOO) {
    // doSomething
})(jQuery, YAHOO);

這裏的module1模塊中需要使用jQuery庫和YUI庫,於是就把這兩個庫(其實是兩個模塊)當作參數輸入module1。這樣做除了保證模塊的獨立性,也使得模塊之間的依賴關系變得明顯。

模塊化的幾種規範

模塊化的前提是要遵循同一套規範,否則模塊之間的調用會十分困難。

JavaScript官方沒有模塊化的規範,目前通用的民間規範主要有CommonJS(服務端js模塊化的規範,NodeJS是這種規範的實現)、AMD(Asynchronous Module Definition異步模塊定義,RequireJS遵循此規範)和CMD(Common Module Definition,通用模塊定義,SeaJS遵循此規範)。

模塊化在服務端的規範:CommonJS

2009年,美國程序員Ryan Dahl創造了node.js項目,將JavaScipt語言用於服務器端編程(後端)。這標誌著JavaScipt模塊化編程正式誕生。因為老實說,在瀏覽器環境下,沒有模塊也不是特別大的問題,畢竟網頁程序的復雜性有限;但是在服務器端,一定要有模塊,與操作系統和其他應用程序互動,否則根本沒法編程。

node.js的模塊系統,就是參照CommonJS規範實現的。在CommonJS中,有一個全局性的方法require(),用於加載模塊。假定有一個數學模塊math.js,就可以像下面這樣加載並調用模塊中提供的方法:

var math = require(‘math‘);
math.add(2, 3); // 5

更多的用法這裏就不說了,只需要知道CommonJS是使用require()函數加載模塊就行了。

模塊化規範從服務端到客戶端的發展

有了服務端的模塊化之後,大家就想要客戶端的模塊化了。而且最好兩者能兼容,一個模塊不用修改,在服務器和瀏覽器都可以運行。但是由於一個重大局限,使得CommonJS規範不適用於瀏覽器環境。這是因為,在上面的代碼中,調用math的方法必須要在math.js加載完成。也就是說,如果加載時間很長,整個應用就會停在那裏等。

這對於服務器端不是一個問題,因為所有的模塊都存放在本地硬盤,可以同步加載完成,等待時間就是硬盤的讀取時間。但是對於瀏覽器來說,這卻是一個大問題,因為模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,可能會導致瀏覽器處於假死狀態。

因此瀏覽器端的模塊化不能使用同步加載(Synchronous),只能使用異步加載(Asynchronous)。這就是AMD規範誕生的背景。

模塊化在客戶端的規範:AMD

AMD(Asynchronous Module Definition,異步模塊定義)采用異步方式加載模塊,模塊的加載不影響它後面語句的運行。所有依賴這個模塊的語句,都定義在一個回調函數中,直到加載完成之後,這個回調函數才會運行。

AMD也采用require()語句加載模塊,不同於CommonJS的是,它要求兩個參數:

require([module], callback);

第一個參數[moudle],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback,則是加載成功之後的回調函數。如果將前面的代碼改寫成AMD形式,就是這樣:

require([‘math‘], function(math) {
    math.add(2, 3);
});

這樣,math.add()與math模塊的加載就不是同步的,瀏覽器也不會發生假死的狀況。所以很顯然地是AMD比較適合瀏覽器環境。應用AMD規範的主要有require.js。

模塊化在客戶端的規範:CMD

CMD是SeaJS在推廣過程中對模塊定義的規範化產出。

AMD和CMD的區別:

1.對於依賴的模塊,AMD是提前執行,CMD是延遲執行。CMD推崇的是as lazy as possible,即盡可能得懶加載(延遲加載),即在需要得時候才加載。

2.CMD推崇依賴就近,AMD推崇依賴前置。

// CMD
define(function(require, exports, module) {
    var a = require(‘./a‘);
    a.doSomething();
    var b = require(‘./b‘);   // 依賴可以就近書寫
    b.doSomething();
})

// AMD 默認推薦的是
define([‘./a‘, ‘./b‘], function(a, b) { // 依賴必須一開始就寫好
    a.doSomething();
    b.doSomething();
}) 

3.AMD的API默認是一個當多個用,CMD的API則是嚴格區分,推崇職責單一。比如在AMD裏,require分全局和局部,而在CMD裏則沒有全局require,而是根據模塊系統的完備性,提供seajs.use來實現模塊系統的加載啟動。CMD裏,每個API都簡單存粹。

模塊化的優點總結

1.解決了命名的沖突問題。多人開發的場景下,容易出現命名沖突,模塊化通過內部封裝與外部隔離能有效防止命名沖突的問題。

2.解決了文件的依賴問題,使文件易於管理。如果有很多js文件相互依賴,依賴關系和加載順序都是讓人頭冷的問題。使用模塊化就可以很好地實現依賴管理(使用依賴都要提前聲明)。

3.提高代碼的可讀性。各個模塊各自完成自己的功能,專司其職,除了問題也會便於維護。

4.提高代碼的復用性。可以抽提特定的通用功能作為一個通用的模塊。

"可是怎麽辦,想起你的時候,心還是會疼。"

js模塊化編程思想、實現與規範