js模塊化

來自:http://www.cnblogs.com/syfwhu/p/4883532.html

前言

模塊是任何大型應用程序架構中不可缺少的一部分,模塊可以使我們清晰地分離和組織項目中的代碼單元。在項目開發中,通過移除依賴,鬆耦合可以使應用程序的可維護性更強。與其他傳統編程語言不同,在當前JavaScript裏,並沒有提供原生的、有組織性的引入模塊方式。本文就來探討一下目前的常見幾種模塊化解決方案。

1.對象字面量表示法

對象字面量可以認爲是包含一組鍵值對的對象,每一對鍵和值由冒號分隔。對象字面量不需要使用new運算符進行實例化,在對象的外部也可以給對象添加屬性和方法。示例如下:

複製代碼
 1 var myModule = {
 2     myProperty: "jeri",
 3 
 4     // 對象字面量可以包含屬性和方法
 5     // 例如,可以聲明模塊的配置對象
 6     myConfig: {
 7         useCaching: true,
 8         language: "en"
 9     },
10 
11     // 基本方法
12     myMethod1: function () {
13         console.log("method1");
14     },
15 
16     // 根據當前配置輸出信息
17     myMethod2: function () {
18         console.log("Caching is:" + '(this.myConfig.useCaching) ? "enabled" : "disabled"');
19     },
20 
21     // 根據當前配置輸出信息
22     myMethod3: function (newConfig) {
23         
24         if (typeof newConfig === "object") {
25             this.myConfig = newConfig;
26             console.log(this.myConfig.language);
27         }
28     }
29 }
複製代碼

 如上所述,使用對象字面量有助於封裝和組織代碼,然後不同的對象字面量模塊再構成複雜的項目。

2.Module模式

Module模式最初定義在傳統的軟件工程中,爲類提供私有和公有封裝的方法。在JavaScript中,並不能可以直接聲明類,但我們可以使用閉包來封裝私有的屬性和方法,進而模擬類的概念,在JavaScript中實現Module模式。通過這種方式,我們就使得一個單獨的對象擁有公有/私有方法和變量,從而屏蔽來自全局作用域的特殊部分,也就大大降低了變量聲明之間和函數聲明之間衝突的可能性。

複製代碼
 1 var myModule = (function () {
 2 
 3     // 私有變量
 4     var privateVar = 0;
 5 
 6     // 私有函數
 7     var privateFun = function (foo) {
 8         console.log(foo);
 9     };
10 
11     return {
12         // 私有變量
13         publicVar: "foo",
14 
15         // 公有函數
16         publicFun: function (arg) {
17 
18             // 修改私有變量
19             privateVar ++;
20 
21             // 傳入bar調用私有方法
22             privateFun(arg);
23         }
24     };
25 }) ();
複製代碼

如上所示,通過使用閉包我們封裝了私有變量和方法,而只暴露了一個接口供其他部分調用。私有變量(privateVar)和方法(privateFun)被侷限於模塊的閉包之中,只有通過公有方法才能訪問。該模式除了返回的是一個對象而不是一個函數之外,非常類似於一個立即調用函數表達式,我們可以爲返回的對象添加新的屬性和方法,這些新增的屬性和方法對外部調用者來說都是可用的。

Module模式的這種JavaScript實現對於具有面向對象開發經驗的人來說非常簡潔,但其也有自身的缺點和劣勢。

由於我們訪問公有和私有成員的方式不同,當我們想改變可見性時,我們需要修改每一個曾經使用該成員的地方,並不利於維護和升級,耦合度並不理想。而且,在之後新添加的方法裏,我們並不能訪問以前聲明的私有方法和變量,因爲閉包只在創建時完成綁定。我們也無法爲私有方法創建自動化單元測試,修正私有方法也是極其困難的,我們需要複寫所有與私有方法交互的公有方法,bug修正時工作量會很大。另外,我們也不能輕易的擴展私有方法。

關於腳本加載器

要討論 AMD 和 CommonJS 模塊,我們必然會談及一個顯而易見的話題——腳本加載器。目前,腳本加載是爲了讓我們能在現今的各種應用中都能使用模塊化的 JavaScript 這個目標而服務的。有很多加載器用於 AMD 和 CommonJS方式中的模塊加載,比較出名的有RequireJS 和 curl.js。關於腳本加載器的使用方式和運行機制,大家可以自行了解一下。

3.AMD模塊

AMD全稱是Asynchronous Module Definition,即異步模塊加載機制。它誕生於使用XHR+eval的Dojo開發經驗,其整體目標是提供模塊化的JavaScript解決方案,避免未來的任何解決方案受到過去解決方案缺點的影響。AMD模塊格式本身就是對定義模塊的建議,其模塊和依賴都可以進行異步加載,而且具有高度的靈活性,清除了代碼和模塊之間可能慣有的緊耦合。

關於AMD有兩個非常重要的概念,那就是用於模塊定義的define方法和用於處理依賴加載的require方法。

作爲一個規範,只需定義其語法API,而不關心其實現。define函數定義如下:
1 define(
2     [module-name?] /*可選*/, 
3     [array-of-dependencies?] /*可選*/, 
4     [module-factory-or-object]
5 );
其中:
  • module-name: 模塊標識,可以省略。如果沒有這個屬性,則稱爲匿名模塊。
  • array-of-dependencies: 所依賴的模塊,可以省略。
  • module-factory-or-object: 模塊的實現,或者一個JavaScript對象。

具體示例如下:

複製代碼
 1 define(
 2     "myModule",
 3     ["foo", "bar"],
 4 
 5     // 模塊定義函數,依賴(foo,bar)作爲參數映射到函數上
 6     function (foo, bar) {
 7         // 創建模塊
 8         var myModule = {
 9             myFun: function () {
10                 console.log("Jeri");
11             }
12         }
13 
14         // 返回定義的模塊
15         return myModule;
16     }
17 );
複製代碼

require用於加載JavaScript文件或模塊的代碼,獲取依賴。示例如下:

複製代碼
1 // foo,bar爲外部模塊,加載以後的輸出作爲回調函數的參數傳入,以便訪問
2 requrie(["foo", "bar"], function (foo, bar) {
3 
4     // 其他代碼
5     foo.doSomething();
6 });
複製代碼

下面是一個動態加載依賴的示例:

複製代碼
 1 define(
 2     function (requrie) {
 3         var isReady = false,
 4         foobar;
 5 
 6         requrie(["foo", "bar"], function (foo, bar) {
 7             isReady = true,
 8             foobar = foo() + bar();
 9         });
10 
11         // 返回定義的模塊
12         return {
13             isReady: isReady,
14             foobar: foobar
15         };
16     }
17 );
複製代碼

AMD模塊可以使用插件,也就是說當我們加載依賴時,可以加載任意格式的文件。AMD對於如何完成靈活模塊的定義提供了明確的建議,使用AMD編寫模塊化的JS代碼,比現有的全局命名空間和<script>標籤解決方案更加簡潔,沒有全局命名空間污染,在需要的時候也可以延遲加載腳本。

4.CommonJS模塊

CommonJS規範建議指定一個簡單的API來聲明在瀏覽器外部工作的模塊。與AMD不同,它試圖包含更廣泛的引人關注的問題,如IO、文件系統等。

從結構來看,CommonJS模塊是JS中可以複用的部分,導出特定對象,以便可以用於任何依賴代碼。與AMD表現形式不同的是,CommonJS模塊並不使用define進行定義。CommonJS模塊由兩部分組成:變量exports和require函數。exports包含了一個模塊希望其他模塊能夠使用的對象,require函數用來導入其他模塊的導出,也就是用來加載其他模塊依賴。示例如下:

lib.js

複製代碼
1 // 新定義的模塊方法
2 function log(arg) {
3     console.log(arg);
4 }
5 
6 // 把方法暴露給其他模塊
7 exports.log = log;
複製代碼

app.js

複製代碼
 1 // ./lib是我們需要的一個依賴
 2 var lib = requrie("./lib");
 3 
 4 // 新定義的模塊方法
 5 function foo() {
 6     lib.log("jeri");
 7 }
 8 
 9 // 把方法暴露給其他模塊
10 exports.foo = foo;
複製代碼

雖然在瀏覽器端可以使用CommonJS組織模塊,但有不少開發者認爲CommonJS更適合於服務器端開發,因爲很多CommonJS API具有面向服務器的特性,如io、system等。NodeJs使用的就是CommonJS規範。當一個模塊可能用於服務器端時,一些開發人員傾向於選擇CommonJS,其他情況下使用AMD。

AMD模塊可以使用插件,也就是說當我們加載依賴時,可以加載任意格式的文件,並且可以定義更細粒度的東西,如構造函數和函數,但CommonJS模塊僅能定義不易使用的對象。在模塊的定義和引入方面,二者也有很大的不同。AMD和CommonJS都是非常優秀的模塊模式,各自有不同的目標。

  • AMD採用採用瀏覽器優先的開發方法,選擇異步行爲和簡化的後向兼容性,但沒有任何的文件I/O概念。支持對象、函數、構造函數以及其他類型的對象,在瀏覽器中原生運行。
  • CommonJS採用服務器優先的方法,假定同步行爲,沒有全局概念負擔,僅將對象作爲模塊給予支持。CommonJS支持非包裝模塊,更接近下一代ES Harmony規範。

5.ES Harmony模塊

TC39——負責制定 ECMAScript 語法和語義以及其未來迭代的標準團體,在近幾年一直在密切關注 JavaScript 在大規模開發中的使用情況的演進,而且也敏感地意識到了需要有更好的語言特性來編寫更加模塊化的 JS。基於這個原因,目前有提案已經提出了一系列令人振奮的對語言的補充。雖然Harmony還處於建議階段,但我們可以先一覽新的接口特性。

Imports和Exports模塊

在ES.next中,已經爲模塊依賴和模塊導出提供了更加簡潔的方式,那就是import和export。

  • import聲明綁定一個模塊,作爲局部變量導出,並能被重命名,以避免名稱衝突。
  • export聲明一個外部可見模塊的本地綁定,其它模塊能夠讀取這些導出,但無法進行修改。模塊可以導出子模塊,但不能導出再其他地方定義的模塊。導出也是可以重命名的。

cakes.js

複製代碼
 1 module staff {
 2     // 指定導出
 3     export var baker = {
 4         bake: function(item) {
 5             console.log('Woo! I just baked ' + item);
 6         }
 7     }
 8 };
 9 
10 module skills {
11     export var specialty = "baking";
12     export var experience = "5 years";
13 };
14 
15 module cakeFactory {
16     // 指定依賴項
17     import baker from staff;
18 
19     // 通過通配符導入所有東西
20     import * from skills;
21 
22     export var oven = {
23         makeCupcake: function(toppings) {
24             baker.bake('cupcake', toppings);
25         },
26         makeMuffin: function(mSize) {
27             baker.bake('muffin', size);
28         }
29     }
30 };
複製代碼

遠程加載模塊

在ES.next裏還建議支持遠程模塊加載,示例如下:

1 module cakeFactory from 'http://****/cakes.js';
2 
3 cakeFactory.oven.makeCupcake('sprinkles');
4 
5 cakeFactory.oven.makeMuffin('large');

模塊加載器 API

模塊加載器建議一個動態的API在嚴格控制的上下問中加載模塊。加載器支持的特徵包括用來加載模塊的load( url, moduleInstance, error),以及createModule( object, globalModuleReferences)等等。

用於服務器的類 CommonJS 模塊

對於面向服務器的開發者來說,在 ES.next 中提出的模塊系統並非侷限於對瀏覽器端模塊的關注。例如下面是一個在服務器端使用的類CommonJS模塊:

File.js

複製代碼
1 // io/File.js
2 export function open(path) {
3     // ...
4 };
5 export function close(hnd) {
6     // ...
7 };
複製代碼

LexicalHandler.js

複製代碼
 1 // compiler/LexicalHandler.js
 2 module file from 'io/File';
 3 
 4 import {open, close} from file;
 5 export function scan( in ) {
 6     try {
 7         var h = open( in )...
 8     } finally {
 9         close(h)
10     }
11 }
複製代碼

app.js

1 module lexer from 'compiler/LexicalHandler';
2 module stdlib from '@std';
3  
4 // ... scan(cmdline[0]) ...

ES Harmony有了很多令人振奮的新功能加入,以求簡化應用程序的開發,並處理依賴管理等問題。然而,至今爲止,還沒有形成新的規範,並不能得到衆多瀏覽器的支持。目前,要想使用Harmony語法的最佳選擇是通過transpiler,如谷歌的Traceur或Esprima。在新規範發佈之前,我們還是選擇AMD和CommonJS較爲穩妥。

寫在最後

本文論述了幾種模塊化編程的方式,它們各有優劣,各有適用場景。希望在以後的編程實踐中,選擇合適的模式,不斷提高代碼的可讀性、可維護性和可擴展性。

發佈了45 篇原創文章 · 獲贊 24 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章