JS模塊化開發:使用SeaJs高效構建頁面

1、扯淡部分javascript

好久好久之前,也就是剛開始接觸前端的那會兒,腦殼裏壓根沒有什麼架構、重構、性能這些概念,天真地覺得前端===好看的頁面,甚至把js都劃分到除了用來寫一些美美的特效別無它用的陰暗角落裏,就更別說會知道js還有面向對象,設計模式,MVC,MVVM,模塊化,構建工具等等這些高大上的概念了。如今想一想還真是Too young too naive。前兩天某大神在羣裏分享他招聘前端的心得的時候就說,就是那些覺得能寫兩個頁面就能夠自稱前端的人拉低了行業水平。這樣看來前兩年我還真的扯了很多後腿呢……html

後來幹這行幹得稍久一些,發現水簡直深深深深千尺,並且周圍遍及沼澤。即使爬到岸上,迎接你的又是大大小小各類坑。坑爹的IE6,坑爹的兼容,坑爹的瀏覽器特性……總之,任何一個前端都有被這些大大小小的坑虐到體無完膚的慘痛經歷。但(我以爲這個但字是點睛之筆),生活在繼續,時代在發展,競爭依然殘酷,你不往前走就只能在這片沼澤裏不斷下沉,最後掙扎的結果也不過是冒出水面兩個泡泡而後……爆掉。前端

在經歷了會寫頁面,會用js寫效果的階段後,大多數人都已經慢慢地可以知足產品提出的各類奇葩的功能需求,但僅僅是知足了需求,而沒有考慮性能、團隊協做、開發消耗的各類成本等等這些問題。有時候甚至寫好的js再回頭去看時也會讓本身一頭霧水:各類方法,各類邏輯雜亂無章地糾纏在一塊兒,根本理不清誰調用了誰,誰爲誰定義,誰又是誰的誰!更可怕的是當項目被其餘小夥伴接管,每修改一處上線前都擔驚受怕:修改這裏到底TM對不對啊?java

還好前端領域開路者們用他們的智慧朝咱們艱難跋涉的水坑裏扔了幾塊石頭:嘗試讓你的代碼模塊化吧~node

 

2、js模塊化git

爲毛要嘗試模塊化開發?github

現在的網頁愈來愈像桌面程序,網頁上加載的javascript也愈來愈複雜,coder們不得不開始用軟件工程的思惟去管理本身的代碼。Javascript模塊化編程,已經成爲一個很是迫切的需求。理想狀況下,開發者只須要實現核心的業務邏輯,其餘均可以加載別人已經寫好的模塊。可是,Javascript不是一種模塊化編程語言,它不支持"類"(class),更遑論"模塊"(module)了。(正在制定中的ECMAScript標準第六版將正式支持"類"和"模塊",但還須要很長時間才能投入實用。)web

——來自阮一峯的博文:《Javascript模塊化編程(一):模塊的寫法編程

上面其實已經把模塊化的意義和目的已經講述的很清楚了,因此就拿來主義,節省腦細胞留給下面的內容設計模式

模塊化的概念出來之後,新的問題又來了:需不須要一個統一的模塊化標準?咱們來試想一下若是沒有標準的狀況:A以本身的標準寫了模塊Module1,而後B又以本身的標準寫了Module2,恩,在他們看來,這的確是模塊,但當Module1想調用模塊Module2的時候該怎麼調用呢?它們之間火星人與地球人交流,沒有同聲傳譯看起來依舊是毫無頭緒。因而模塊化規範便又成了一個問題。

2009年美國的一位大神發明了node.js (具體內容自行腦補,本文不做討論),用來開發服務器端的js。咱們都知道,傳統的服務器端開發語言如PHP、JAVA等都必須進行模塊化開發,JS想佔據人家的地盤也不例外,模塊化是必須的,因而commomJS模塊化開發規範誕生了,但這貨只是服務器端JS模塊化開發的標準,客戶端又沒用。

—有童鞋:bla了那麼多,這跟我在客戶端進行js模塊化開發有毛關係啊?

—PO主:表着急,瞭解了這玩意兒的前世此生,用起來才能駕輕就熟~

服務器端JS模塊化規範有了,JSer們天然想到了能把commonJS規範拿到客戶端就好啦,並且最好二者可以兼容,一個模塊不用修改,在服務器和瀏覽器均可以運行。爽爆~但(這個但字又是一個點睛之筆),因爲一個重大的侷限,使得CommonJS規範不適用於瀏覽器環境。服務器端獲取資源的方式是本地讀取,而客戶端拿資源的方式是經過Http來獲取,這是一個大問題,由於模塊都放在服務器端,瀏覽器等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。所以,瀏覽器端的模塊,不能採用"同步加載"(synchronous),只能採用"異步加載"(asynchronous),因而誕生了AMD和CMD。

—有童鞋:核心內容終於TMD來了,就是AMD和CMD這二貨

—PO主:……

 

3、AMD和CMD

AMD (Asynchronous Module Definition) :  RequireJS 在推廣過程當中對模塊定義的規範化產出。

AMD用白話文講就是 異步模塊定義,對於 JSer 來講,異步是再也熟悉不過的詞了,全部的模塊將被異步加載,模塊加載不影響後面語句運行。全部依賴某些模塊的語句均放置在回調函數中,等到依賴的模塊加載完成以後,這個回調函數纔會運行。

主要有兩個Javascript庫實現了AMD規範:require.jscurl.js

(本文主要分享的是SeaJs模塊化構建方式,關於requireJs構建方式請移步至:《Javascript模塊化編程(一):模塊的寫法》)

 

CMD (Common Module Definition) : SeaJS 在推廣過程當中對模塊定義的規範化產出。

實現了CMD規範的主要的Javascript庫:Sea.js

CMD翻譯來就是 通用模塊定義,與AMD的相同點:

1. 這些規範的目的都是爲了 JavaScript 的模塊化開發,特別是在瀏覽器端的。

2. 目前這些規範的實現都能達成瀏覽器端模塊化開發的目的

固然與AMD也有有兩點區別:

1. 對於依賴的模塊,AMD 是提早執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改爲能夠延遲執行(根據寫法不一樣,處理方式不一樣)。CMD 推崇 as lazy as possible(PO主:是越懶越好的意思麼?)。

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

——SeaJs做者玉伯在知乎的回答

看代碼理解上面兩點的意思:

AMD模塊的定義方法

// AMD 默認推薦的是
define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好,即依賴前置,執行完引入的模塊後纔開始執行回調函數
    a.doSomething()
    // 此處略去 100 行
    b.doSomething()
    ...
})

CMD模塊的定義方法:

// CMD
define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()
    // 此處略去 100 行
    var b = require('./b') // 依賴能夠就近書寫,即依賴就近,何時用到何時才引入
    b.doSomething()
    // ... 
})

好了,看過兩個例子,對於以前沒有接觸過模塊化開發的童鞋來講依舊是一頭霧水:那個define是什麼東東啊?還有那個require,exports,module,都是幹什麼的?表捉急,咱們一步一步來。

在 CMD 規範中,一個模塊就是一個文件。代碼的書寫格式以下:

define(factory);

來看github上CMD模塊定義規範上的解釋:

define 是一個全局函數,用來定義模塊。

define 接受 factory 參數,factory 能夠是一個函數,也能夠是一個對象或字符串

factory 爲對象、字符串時,表示模塊的接口就是該對象、字符串。好比能夠以下定義一個 JSON 數據模塊:

1 define({ "foo": "bar" });

也能夠經過字符串定義模板模塊:

1 define('I am a template. My name is {{name}}.');

factory 爲函數時,表示是模塊的構造方法。執行該構造方法,能夠獲得模塊向外提供的接口。factory 方法在執行時,默認會傳入三個參數:require、exports 和 module

1 define(function(require, exports, module) {
2   // 模塊代碼
3 });
 
4、小例子
 
說了半天概念應該印象還不深入,咱們就來看一個例子用來演示sea.js的基本用法。首先define傳入的參數是對象和字符串的狀況,我先舉一個參數的對象的例子,傳字符串大同小異。來看代碼:
 
1,我先來定義一個模塊m1.js:
define({a:"這裏是屬性a的值"});

define傳入的是一個對象字面量。如今這個東東就能夠叫作一個模塊了~我想在頁面一加載的時候就把a的值alert出來,怎麼作呢?繼續往下看。

2,在頁面上引入這個模塊:

1 seajs.use('./m1.js',function(ex){
2      alert(ex.a);
3  }); //彈出「這裏是屬性a的值」

翻譯得直白一點,大意就是:

seajs : Hi~m1.js,我如今要用(use)你了,而後把你的公開接口(exports)存到我回調函數的參數(ex)裏,你把想給我調用的東東放到這個參數裏吧~麼麼噠

m1.js : 好的,我定義的對象字面量放到接口裏給你了,拿去儘管刷~

而後……a的值就彈出來了。很愉快的一次交易。PS:頁面所調用的模塊就爲整個web應用的js入口。本例中js的入口就是m1.js。接下來再來看看若是define的參數是個函數的狀況。
 
1,先定義一個模塊m2.js:
1 define(function(require,exports,module){
2     var var1 = "這是要alert出來的值";//私有變量,沒有經過接口返出去的其餘模塊不能訪問
3     function alerts(){
4         alert(var1);
5     }
6     exports.alerts = alerts;//將須要公開的方法存入exports接口中
7 });

2,在頁面上引入這個模塊並執行模塊m2.js公開的方法:

1 seajs.use('./m2.js',function(ex){
2      ex.alerts();//ex中存的有m2.js中的公開對象
3 }); //彈出「這是要alert出來的值」

到這裏能夠簡單地說一下factory方法的三個形參的意義了(我的理解):

require : 提供了引入機制,提供了一種方式來創建依賴,和C中的include和java中的import相似;

exports : 提供了導出機制,提供了私有和共有分離,未使用exports語句導出的變量或者函數,其餘模塊即便引用此模塊也不能使用;

module : 提供了模塊信息描述。

是否是思路賤賤清晰了呢?剛纔咱們的例子中只是從頁面調用模塊的用法,模塊之間互相調用尚未體現,SO,接下來就以m1.js和m2.js兩個模塊做爲例子來嘗試一下 模塊之間互相調用

1,首先m1.js模塊不變:

1 define({a:"這裏是屬性a的值"});

2,m2.js模塊要依賴(require)m1.js:

1 define(function(require,exports,module){
2     var var1 = "這是要alert出來的值";//私有變量,沒有經過接口返出去的其餘模塊不能訪問
3     var var2 = require('./m1.js').a;//這裏就是m2.js模塊調用m1.js的方式:var2的值等於當前模塊所依賴的m1.js對外接口中屬性a的值
4     function alerts(){
5         alert(var2);
6     }
7     exports.alerts = alerts;//將須要公開的方法存入exports接口中
8 });

3,頁面上引入m2.js模塊(同上一個例子),結果就會把a的屬性值給alert出來~

 

5、實例:模塊化的拖拽個窗口縮放

 

固然,上面幾個例子是簡單到不能再簡單的例子,估計親們也已經看出來一些道道,但我的感受仍是沒能體現出模塊化開發的優點。那下面就來看一個實例:模塊化的拖拽個窗口縮放。先看一下效果圖:

PS:效果圖中的紅色區域要先定縮放的範圍,即寬高0px-寬高500px。要寫這樣一個需求的例子,按照以前的編程習慣你會怎麼寫?反正在以前,我是會把全部的功能寫到一個js文件裏,效果出來就行,隨大家怎麼胡攪蠻纏。而自從認識了模塊化開發,心裏不止一次告訴本身,拿到需求bigger必定要高,必定要高(雖然require.js和sea.js這兩個東東在圈內多多少少仍是有些爭議)……

廢話少說,首先來分析一下須要劃分多少個模塊吧:

1,一開始就要有個入口模塊的吧?恩,必須的!入口模塊Get√~

2,既然是拖拽,要有個拖拽模塊吧?恩,必須的!拖拽模塊Get√~

3,既然要縮放,要有個縮放模塊吧?恩,必須的!縮放模塊Get√~

4,既然限定縮放範圍<=500px,那還要有個限定縮放範圍的模塊吧?恩,這個能夠有,但爲了之後調整範圍數值方便,仍是單列個模塊吧。限定縮放範圍模塊Get√~

到這裏咱們就把本需求劃分紅了四個模塊:

·  入口模塊:main.js

·  拖拽模塊:drag.js

·  縮放模塊:scale.js

·  限定縮放範圍模塊:range.js

首先,是頁面引入入口模塊(我儘可能把註釋都寫在代碼中,以便對照代碼,這樣也就不用寫大片大片的文字了~):
1  <script>
2     seajs.use('./js/main.js');//沒有callback函數代表引入後直接執行入口模塊
3 </script>

接下來看看入口模塊(main.js)裏都應該有些神馬東東吧:

 1 //入口模塊
 2 define(function(require,exports,module){
 3     var $id = function(_id){return document.getElementById(_id);}
 4     var oInput = $id("button1");
 5     var div1 = $id("div1");
 6     var div2 = $id("div2");
 7     var div3 = $id("div3");//以上是獲取頁面元素的幾隻變量
 8     require('./drag.js').drag(div3);//引入拖拽模塊,執行拖拽模塊接口中的drag方法並傳參
 9     exports.oInput = oInput;
10     oInput.onclick = function(){
11         div1.style.display = "block";
12         require('./scale.js').scale(div1,div2);//引入縮放模塊,執行縮放模塊接口中的scale方法並傳參
13     }
14 });

恩,還真是全面呢,把拖拽模塊和縮放模塊都引進來了。看看拖拽模塊(drag.js)吧~

 1 //拖拽模塊
 2 define(function(require,exports,module){
 3     //這個方法就是實現拖拽的方法,不用詳述了吧?
 4     function drag(obj){
 5         var disX = 0;
 6         var disY = 0;
 7         obj.onmousedown = function(e){
 8             var e = e || window.event;
 9             disX = e.clientX - obj.offsetLeft;
10             disY = e.clientY - obj.offsetTop;
11             document.onmousemove = function(e){
12                 var e = e || window.event;
13                 var l = require('./range.js').range(e.clientX - disX, document.documentElement.clientWidth - obj.offsetWidth,0);
14                 var t = require('./range.js').range(e.clientY - disY, document.documentElement.clientHeight - obj.offsetHeight,0);
15                 obj.style.left = l + "px";
16                 obj.style.top = t + "px";
17             }
18             document.onmouseup = function(){
19                 document.onmousemove = null;
20                 document.onmouseup = null;
21             }
22         }
23     }
24     exports.drag = drag;//返回拖拽模塊中想要被公開的對象,也就是在本模塊中定義的drag方法。注意有參數~
25 });

接下來是縮放模塊(scale.js)。縮放模塊還須要調用 限定縮放範圍模塊 (range.js) 的哦~這點不要搞忘了。

 1 //縮放模塊
 2 define(function(require,exports,module){
 3     //這個方法就是obj2控制obj1改變大小的方法,也再也不詳述啦~
 4     function scale(obj1,obj2){
 5         var disX = 0;
 6         var disY = 0;
 7         var disW = 0;
 8         var disH = 0;
 9         obj2.onmousedown = function(e){
10             var e = e || window.event;
11             disX = e.clientX;
12             disY = e.clientY;
13             disW = obj1.offsetWidth;
14             disH = obj1.offsetHeight;
15             document.onmousemove = function(e){
16                 var e = e || window.event;
17                 var w = require('./range.js').range(e.clientX - disX + disW,500,100);//看這裏看這裏,引入了限定範圍的range.js模塊~
18                 var h = require('./range.js').range(e.clientY - disY + disH,500,100);
19                 obj1.style.width = w + "px";
20                 obj1.style.height = h + "px";
21             }
22             document.onmouseup = function(){
23                 document.onmousemove = null;
24                 document.onmouseup = null;
25             }
26         }
27     }
28     exports.scale = scale;//將須要公開的對象存入模塊接口中,以便其餘模塊調用~
29 });

最後就是限定範圍的模塊(range.js)了。

 1 //限定拖拽的範圍模塊
 2 define(function(require,exports,module){
 3     function range(inum,imax,imin){
 4         if(inum > imax){
 5             return imax;
 6         }else if(inum < imin){
 7             return imin;
 8         }else{
 9             return inum;
10         }
11     }
12     exports.range = range;
13 });

這就是模塊化,雖然在這個實例中咱們用到了4個js,但在頁面上咱們只引入了一個入口模塊main.js,其餘模塊都會按需自動引入(以下圖所示),並且每一個功能模塊的區分特別清晰,不再用擔憂神馬命名衝突啊、依賴混亂啊之類的,並且團隊小夥伴每人負責一個模塊,只要放出當前模塊的公開接口並提供簡要的說明文檔(由於標準統一),其餘小夥伴們寫的模塊就能很是方便地調用到你寫的模塊,連修改的時候都不用考慮對其餘功能的影響,變得更大膽了呢~

查看完整DEMO請猛戳

 
寫在最後
 
其實本文介紹的模塊化和seajs的使用依舊比較淺顯,但基本的模塊化思想已經融入到例子中了。 若是你經歷過前文所述的之前寫js邏輯的各類糾結各類坑爹,不妨嘗試一下將你的代碼模塊化,那將是一種飛同樣的感受……本文最後會爲你們列出一些相關的資料,想深刻了解的小夥伴們能夠果斷收走~
 
SeaJs官網 : http://seajs.org/docs/

CMD 模塊定義規範:https://github.com/seajs/seajs/issues/242

玉伯:AMD和CMD的區別:http://www.zhihu.com/question/20351507/answer/14859415 

AMD and CMD are dead之js模塊化黑魔法 : http://www.cnblogs.com/iamzhanglei/p/3790346.html

(後續會繼續補充……)

相關文章
相關標籤/搜索