javascript模塊化編程 從入門到實戰

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>javascript模塊化編程</title>
</head>
<body>
    <p></p>
網頁愈來愈像桌面程序,須要一個團隊分工協做、進度管理、單元測試等等......開發者不得不使用軟件工程的方法,管理網頁的業務邏輯。

Javascript模塊化編程,已經成爲一個迫切的需求。理想狀況下,開發者只須要實現核心的業務邏輯,其餘均可以加載別人已經寫好的模塊。

可是,Javascript不是一種模塊化編程語言,它不支持"類"(class),更遑論"模塊"(module)了。(正在制定中的ECMAScript標準第六版,將正式支持"類"和"模塊",但還須要很長時間才能投入實用。)

Javascript社區作了不少努力,在現有的運行環境中,實現"模塊"的效果。本文總結了當前"Javascript模塊化編程"的最佳實踐,說明如何投入實用。雖然這不是初級教程,可是隻要稍稍瞭解Javascript的基本語法,就能看懂。

<h1>模塊就是實現特定功能的一組方法。</h1>
<h2>只要把不一樣的函數(以及記錄狀態的變量)簡單地放在一塊兒,就算是一個模塊。</h2>

<script> /** * 第一部分介紹Javascript模塊的基本寫法 * */ //1、原始寫法 "污染"了全局變量 function m1(){} function m2(){} //2、對象寫法 這樣的寫法會暴露全部模塊成員,內部狀態能夠被外部改寫。 var module = new Object({ _count : 0, m1:function(){}, m2:function m2(){} }); module.m1(); module._count = 5; //3、當即執行函數寫法 外部代碼沒法讀取內部的_count變量。 var module1 = (function(){ var _count = 0; var m1 = function () {} var m2 = function () { console.log("我是module1上的m2,很高興認識你!") } return { m1:m1, m2:m2 } })(); // module1.m2() //module1就是Javascript模塊的基本寫法。下面,再對這種寫法進行加工。 // 4、放大模式 (對原始模塊功能的擴展) // 若是一個模塊很大,必須分紅幾個部分,或者一個模塊須要繼承另外一個模塊 var module2 = (function(mod){ mod.m3 = function(){ console.log("我是module2上的m3,很高興認識你!") } return mod; })(module1); console.log(module2) // 5、寬放大模式(Loose augmentation) // 在瀏覽器環境中,模塊的各個部分一般都是從網上獲取的,有時沒法知道哪一個部分會先加載。若是採用上一節的寫法,第一個執行的部分有可能加載一個不存在空對象,這時就要採用"寬放大模式"。 var module3 = (function(mod){ //... return mod; })(window.module2||{}); // 6、輸入全局變量 // 獨立性是模塊的重要特色,模塊內部最好不與程序的其餘部分直接交互。 // 爲了在模塊內部調用全局變量,必須顯式地將其餘變量輸入模塊。 var jQuery = {}; var module4 = (function(mod){ //... })(jQuery,module3) // 這樣作除了保證模塊的獨立性,還使得模塊之間的依賴關係變得明顯 /** * 第二部分介紹如何規範地使用模塊。 * */ //7、模塊的規範 // 目前,通行的Javascript模塊規範共有兩種:CommonJS和AMD。我主要介紹AMD,可是要先從CommonJS講起。 // node.js的模塊系統,就是參照CommonJS規範實現的。在CommonJS中,有一個全局性方法require(),用於加載模塊。 //8、CommonJS (同步) // 假定的一個math.js模塊,加載方式以下 // var math = require('math'); // 調用 // math.add(2,3); //5 // 9、瀏覽器環境 AMD(異步) //瀏覽器端的模塊,不能採用"同步加載"(synchronous),只能採用"異步加載"(asynchronous)。這就是AMD規範誕生的背景。 // AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。 // AMD也採用require()語句加載模塊,可是不一樣於CommonJS,它要求兩個參數: // require([module], callback); // 第一個參數[module],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback,則是加載成功以後的回調函數。若是將前面的代碼改寫成AMD形式,就是下面這樣: /** * require([module1,module2,...],function(module1,module2,...){ module1.add(2,3); }); */ // 目前,主要有兩個Javascript庫實現了AMD規範:require.js和curl.js。 /** * 第三部分介紹require.js,進一步講解AMD的用法,以及如何將模塊化編程投入實戰。 * */ // 1、爲何要用require.js? // 最先的時候,全部Javascript代碼都寫在一個文件裏面,只要加載這一個文件就夠了。後來,代碼愈來愈多,一個文件不夠了,必須分紅多個文件,依次加載。下面的網頁代碼,相信不少人都見過。 /** * <script src="1.js"><//script>    <script src="2.js"><//script>    <script src="3.js"><//script> 缺點: 1.加載的時候,瀏覽器會中止網頁渲染, 2.因爲js文件之間存在依賴關係,所以必須嚴格保證加載順序, 3.當依賴關係很複雜的時候,代碼的編寫和維護都會變得困難。 * */ //require.js的誕生,就是爲了解決這兩個問題: // (1)實現js文件的異步加載,避免網頁失去響應; // (2)管理模塊之間的依賴性,便於代碼的編寫和維護。 // 2、require.js的加載 // 使用require.js的第一步,是先去官方網站下載最新版本。 // 下載後,假定把它放在js子目錄下面,就能夠加載了。 // <script src="js/require.js"><//script> // 有人可能會想到,加載這個文件,也可能形成網頁失去響應。解決辦法有兩個,一個是把它放在網頁底部加載,另外一個是寫成下面這樣: // <script src="js/require.js" defer async="true" ><//script> // async屬性代表這個文件須要異步加載,避免網頁失去響應。IE不支持這個屬性,只支持defer,因此把defer也寫上。 // 加載require.js之後,下一步就要加載咱們本身的代碼了。假定咱們本身的代碼文件是main.js,也放在js目錄下面。那麼,只須要寫成下面這樣就好了: // <script src="js/require.js" data-main="js/main"><//script> // data-main屬性的做用是,指定網頁程序的主模塊。在上例中,就是js目錄下面的main.js,這個文件會第一個被require.js加載。因爲require.js默認的文件後綴名是js,因此能夠把main.js簡寫成main。 // 3、主模塊的寫法 // 上一節的main.js,我把它稱爲"主模塊",意思是整個網頁的入口代碼。它有點像C語言的main()函數,全部代碼都從這兒開始運行。 // 下面就來看,怎麼寫main.js。 // 主模塊依賴於其餘模塊,這時就要使用AMD規範定義的的require()函數。 // main.js /** * require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){ // some code here }); * */ //4、模塊的加載 // 上一節最後的示例中,主模塊的依賴模塊是['jquery', 'underscore', 'backbone']。默認狀況下,require.js假定這三個模塊與main.js在同一個目錄,文件名分別爲jquery.js,underscore.js和backbone.js,而後自動加載。 //使用require.config()方法,咱們能夠對模塊的加載行爲進行自定義。require.config()就寫在主模塊(main.js)的頭部。參數就是一個對象,這個對象的paths屬性指定各個模塊的加載路徑。 //另外一種則是直接改變基目錄(baseUrl)。 /** *   require.config({     baseUrl: "js/lib",     paths: {       "jquery": "jquery.min",       "underscore": "underscore.min",       "backbone": "backbone.min"     }   }); * */ // require.js要求,每一個模塊是一個單獨的js文件。這樣的話,若是加載多個模塊,就會發出屢次HTTP請求,會影響網頁的加載速度。所以,require.js提供了一個優化工具,當模塊部署完畢之後,能夠用這個工具將多個模塊合併在一個文件中,減小HTTP請求數。 // 5、AMD模塊的寫法 // require.js加載的模塊,採用AMD規範。也就是說,模塊必須按照AMD的規定來寫。 // 具體來講,就是模塊必須採用特定的define()函數來定義。若是一個模塊不依賴其餘模塊,那麼能夠直接定義在define()函數之中。 // 假定如今有一個math.js文件,它定義了一個math模塊。那麼,math.js就要這樣寫: // math.js /** * define(function (){ var add = function (x,y){ return x+y; }; return { add: add }; }); */ // 若是這個模塊還依賴其餘模塊,那麼define()函數的第一個參數,必須是一個數組,指明該模塊的依賴性。 // 當require()函數加載上面這個模塊的時候,就會先加載myLib.js文件。 /** *   define(['myLib'], function(myLib){     function foo(){       myLib.doSomething();     }     return {    foo : foo     };   }); */ // 6、加載非規範的模塊 // 理論上,require.js加載的模塊,必須是按照AMD規範、用define()函數定義的模塊。 // 可是實際上,雖然已經有一部分流行的函數庫(好比jQuery)符合AMD規範,更多的庫並不符合。 // 那麼,require.js是否可以加載非規範的模塊呢? // 回答是能夠的。 // 這樣的模塊在用require()加載以前,要先用require.config()方法,定義它們的一些特徵。 // 舉例來講,underscore和backbone這兩個庫,都沒有采用AMD規範編寫。若是要加載它們的話,必須先定義它們的特徵。 /** * require.config({ shim: { 'underscore':{ exports: '_' }, 'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' } } }); require.config()接受一個配置對象,這個對象除了有前面說過的paths屬性以外, 還有一個shim屬性,專門用來配置不兼容的模塊。 具體來講,每一個模塊要定義 (1)exports值(輸出的變量名),代表這個模塊外部調用時的名稱; (2)deps數組,代表該模塊的依賴性。 */ // 好比,jQuery的插件能夠這樣定義: /** *   shim: {     'jquery.scroll': {       deps: ['jquery'],       exports: 'jQuery.fn.scroll'     }   } * * */ // 7、require.js插件 // require.js還提供一系列插件,實現一些特定的功能。 // domready插件,可讓回調函數在頁面DOM結構加載完成後再運行。 /** *  require(['domready!'], function (doc){     // called once the DOM is ready   }); * * */ // text和image插件,則是容許require.js加載文本和圖片文件。 /** *   define([     'text!review.txt',     'image!cat.jpg'     ],   function(review,cat){       console.log(review);       document.body.appendChild(cat);     }   ); * * */ // 相似的插件還有json和mdown,用於加載json文件和markdown文件。 </script> <script src="js/require.js" defer anync="true" data-main="js/main"></script> </body> </html>

main.jsjavascript

console.log("我加載進來了,哈哈哈哈0.0");

// require(['module1','module2'],function (module1,module2) {
//     module1.m2();
//     // module2.m3();
// })

// require()函數接受兩個參數。第一個參數是一個數組,表示所依賴的模塊,上例就是['moduleA', 'moduleB', 'moduleC'],
// 即主模塊依賴這三個模塊;第二個參數是一個回調函數,當前面指定的模塊都加載成功後,它將被調用。
// 加載的模塊會以參數形式傳入該函數,從而在回調函數內部就可使用這些模塊。
//
// require()異步加載moduleA,moduleB和moduleC,瀏覽器不會失去響應;
// 它指定的回調函數,只有前面的模塊都加載成功後,纔會運行,解決了依賴性的問題。
// 假定主模塊依賴jquery、underscore和backbone這三個模塊,main.js就能夠這樣寫:
/**
 * require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){
        // some code here
    });
 * */


require.config({
    baseUrl:'js/lib/',  //根目錄
    paths:{
        "jquery":"jquery"       //擴展名.js不用寫,會自動加上
    }
})

require(['jquery','../module1'], function ($,module1){
    $("p").html("我是jQuery,我進來了,很高興認識你!")
    module1.m2();
});

module1.jshtml

//無依賴版
// define(function(){
//     var _count = 0;
//     var m1 = function () {}
//     var m2 = function () {
//         console.log("我是module1上的m2,很高興認識你!")
//     }
//     return {
//         m1:m1,
//         m2:m2
//     }
// })

//有依賴
define(['../js/module2'], function(module2){
    var _count = 0;
    var m1 = function () {}
    var m2 = function () {
        console.log("我是module1上的m2,很高興認識你!")
    }
    module2.m3()
    module2.m4 = function(){
        console.log("我是module1上的m4,我很特殊,是module1給module2寫的擴展,很高興認識你!")
    }
    module2.m4();
    return {
        m1:m1,
        m2:m2
    }
});

module2.jsjava

// ;(function(mod){
//     mod.m3 = function(){
//         console.log("我是module2上的m3,很高興認識你!")
//     }
//     return mod;
// })(module1);


define(function(){
    var m3 = function () {
        console.log("我是module2上的m3,很高興認識你!")
    }
    return {
        m3:m3
    }
})

參考資料:node

http://www.ruanyifeng.com/blog/2012/10/javascript_module.htmljquery

http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html編程

http://www.ruanyifeng.com/blog/2012/11/require_js.htmljson

相關文章
相關標籤/搜索