前端模組化的簡單綜述(一)
剛學前端的時候,曾有一段時間很迷糊,不知道為啥突然從html檔案、js檔案和css檔案三件套,變成需要打包在伺服器才能用了。這種不明白感,隨著使用vue,weex等框架逐步熟練之後,降低不少。但依然不知道,這一路究竟發生了什麼。
故在此,梳理整個前端模組的發展過程,為自己的疑惑提供一個完滿的解答。(有不少內容來自對網路上一些大神的部落格整理,本文會在文章最後做出引用和感謝)
目錄
0,模組的定義
模組和模組化:
模組:
在軟體系統設計中,模組是一個擁有明確定義的輸入輸出和特性的程式實體。如果模組的所有輸入都是實現功能必不可少的,所有輸出都有動作產生,那麼該模組即成為具有明確意義的模組。也就是說,如果少了一個輸入,模組就不能實現全部功能,它沒有不必要的輸入,每個輸入都用於產生輸出,每個輸出都是模組執行某一功能的結果,沒有未經模組的轉換就變成輸出的輸入。
總的來說,一般模組具有以下幾種特徵:
- 介面,模組的輸入輸出
- 功能,指模組實現的功能,有什麼作用
- 邏輯,描述模組內部如何實現需求及所需資料
- 狀態,指該模組的執行環境,模組間呼叫與被呼叫關係。
模組化:
模組化就是將程式劃分成若干個獨立的模組,每個模組完成一個特定子功能,每個模組即相對獨立,又相互聯絡,他們共同完成系統指定的各項功能。 模組化的目的是為了降低軟體的複雜性。對軟體進行適當的分解,不但可以降低複雜性,而且可以減少開發工作量,從而降低軟體開發成本。
1, 前端最開始的處理:
最自然的寫法:
function foo(){ //... } function bar(){ //... }
而只將相關的程式碼放一起這種寫法缺點很明顯:
1:大量的函式名會"汙染"全域性變數,無法保證不與其他模組發生變數名衝突,
2:模組成員之間的關係不能清晰看出。
3:沒有私有變數和方法等。
針對問題1:和2:,可以用物件來緩解。
var moduleA = {
foo : function () {},
bar: function () {}
}
但是,這樣的寫法會暴露所有模組成員,內部狀態可以被外部改寫。
針對這個問題,可以採用立即執行函式來處理:
var MODULE = (function () { var my = {}, privateVariable = 1; function privateMethod() { // method that use in module } my.moduleProperty = 1; my.moduleMethod = function () { // module API. }; return my; }());
立即執行函式內部宣告的變數和函式,為模組內部私有,return物件為供外部呼叫的介面。
如此方式的“模組”還可以擴充套件、繼承、新增子模組等等。
如此便形成了前端模組的雛形。
2, 前端為什麼需要模組化。
當下前端的快速發展,對網頁的構建提出了更高要求:
-
Web sites are turning into Web Apps
-
Code complexity grows as the site gets bigger
-
Highly decoupled JS files/modules is wanted
-
Deployment wants optimized code in few HTTP calls
前端通過<script>標籤引入js 來處理複雜邏輯和遠端互動。
當邏輯複雜時,開啟方式如下:
body
script(src="zepto.js")
script(src="jhash.js")
script(src="fastClick.js")
script(src="iScroll.js")
script(src="underscore.js")
script(src="handlebar.js")
script(src="datacenter.js")
script(src="deferred.js")
script(src="util/wxbridge.js")
script(src="util/login.js")
script(src="util/base.js")
script(src="util/city.js")
script(src="util/date.js")
script(src="util/cookie.js")
script(src="app.js")
各個檔案的引入順序非常重要,各個檔案並行載入、DOM 順序即執行順序
而用script標籤來引入js函式檔案存在問題:
- 維護困難
- 依賴模糊
- 請求過多
只用引入<script>標籤帶來的問題,越來越難以滿足前端發展的需求。因此模組化成了一個合理的選項
3,common JS規範
2009年,美國程式設計師Ryan Dahl創造了node.js專案,將javascript語言用於伺服器端程式設計。這標誌"Javascript模組化程式設計"正式誕生。node.js的模組系統,就是參照CommonJS規範實現的。
在有了伺服器端模組以後,很自然地,大家就想要客戶端模組。而且最好兩者相容,一個模組不用修改,在伺服器和瀏覽器都可以執行。但是,由於一個重大的侷限,使得CommonJS規範不適用於瀏覽器環境。
var math = require('math');
math.add(2, 3);
如上面的程式碼,math.add實在require之後執行,因此必須等math.js載入完成。
這對伺服器端不是一個問題,因為所有的模組都存放在本地硬碟,可以同步載入完成,等待時間就是硬碟的讀取時間。但是,對於瀏覽器,因為模組都放在伺服器端,等待時間取決於網速的快慢,可能要等很長時間而使瀏覽器處於"假死"狀態。因此,瀏覽器端的模組,不能採用"同步載入"(synchronous),只能採用"非同步載入"(asynchronous)。這就是AMD規範誕生的背景。
4,AMD規範
RequireJS是一個工具庫,主要用於客戶端的模組管理。它可以讓客戶端的程式碼分成一個個模組,實現非同步或動態載入,從而提高程式碼的效能和可維護性。它的模組管理遵守AMD規範(Asynchronous Module Definition)。
RequireJS的基本思想是,通過define方法,將程式碼定義為模組;通過require方法,實現程式碼的模組載入。
4.1 嵌入網頁
<script data-main="scripts/main" src="scripts/require.js"></script>
程式碼的data-main屬性不可省略,用於指定主程式碼所在的指令碼檔案
4.2 define 方法:定義模組
按照是否依賴其他模組,可以分成獨立模組和非獨立模組。
獨立模組的寫法:
define({
method1: function() {},
method2: function() {},
});
// 當然也可以寫為如下方式
define(function () {
return {
method1: function() {},
method2: function() {},
};
});
return 返回的是模組的介面。
非獨立模組
define(['module1', 'module2'], function(m1, m2) {
...
});
4.3 require方法:呼叫模組
require(['foo', 'bar'], function ( foo, bar ) {
foo.doSomething();
});
// 當有過多依賴時,可以用commonJS 寫法:
define(
function (require) {
var dep1 = require('dep1'),
dep2 = require('dep2'),
dep3 = require('dep3'),
dep4 = require('dep4'),
...
}
});
上面方法表示載入foo和bar兩個模組,當這兩個模組都載入成功後,執行一個回撥函式。該回調函式就用來完成具體的任務。
require方法也可以用在define方法內部。比如上面的commonJS風格的寫法使用。
5, CMD規範
CMD 即Common Module Definition
通用模組定義,CMD規範是國內發展出來的,CMD有個瀏覽器的實現是玉伯的SeaJS
,SeaJS
要解決的問題和requireJS
一樣,只不過在模組定義方式和模組載入(可以說執行、解析)時機上有所不同。
以下是對seajs使用的簡單介紹:
5.1 seajs頁面的引入
<body>
<h1 id="title">seajs demo</h1>
<script src="sea-module/sea.js"></script>
<script>
seajs.use('./static/main.js');
</script>
</body>
首先是引入sea.js檔案,然後是通過seajs.use載入main.js檔案。main.js是一個“入口”檔案。
5.2 模組的寫法
比如定義一個changeText.js模組
define(function (require, exports, module) {
var textContent = 'message from module changeText';
module.exports = {
text: textContent
}
})
5.3 引入模組
define(function (require, exports, module) {
var changeText = require('../static/changeText.js');
var title = document.getElementById('title');
title.innerHTML = changeText.text;
})
此時,大功告成,會在html頁面上顯示 “message from module changeText"
5.4 別名 (seajs.config, alias)
seajs.config({
alias:{
'changeText':'../static/changeText.js'
}
});
通過config中alias給'../static/changeText.js'
設定了別名changeText
,現在main中引用changeText模組就可以直接寫成這樣了var changeText = require('changeText')
。
5.5 seajs.use回撥函式
seajs.use(['main','jquery'],function(main,$) {
$('#title').after('<button id="show">showText</button>');
$('#show').on('click',function() {
main.showText()
})
});
上述程式碼我們載入了兩個模組,並把它們輸出的物件傳參給main和$變數,通過點選事件呼叫main中的showText方法,而showText方法呼叫了changeText中的init方法。
參考連結: