1. 程式人生 > >js模組化歷程

js模組化歷程

服務端向前端進軍

Modules/1.0規範源於服務端,無法直接用於瀏覽器端,原因表現為: 1. 外層沒有function包裹,變數全暴漏在全域性。如上面例子中increment.js中的add。 2. 資源的載入方式與服務端完全不同。服務端require一個模組,直接就從硬碟或者記憶體中讀取了,消耗的時間可以忽略。而瀏覽器則不同,需要從服務端來下載這個檔案,然後執行裡面的程式碼才能得到API,需要花費一個http請求,也就是說,require後面的一行程式碼,需要資源請求完成才能執行。由於瀏覽器端是以插入<script>標籤的形式來載入資源的(ajax方式不行,有跨域問題),沒辦法讓程式碼同步執行,所以像commonjs那樣的寫法會直接報錯。 所以,社群意識到,要想在瀏覽器環境中也能模組化,需要對規範進行升級。順便說一句,CommonJs原來是叫ServerJs,從名字可以看出是專攻服務端的,為了統一前後端而改名CommonJs。(論起名的重要性~) 而就在社群討論制定下一版規範的時候,內部發生了比較大的分歧,分裂出了三個主張,漸漸的形成三個不同的派別: 1.Modules/1.x派
這一波人認為,在現有基礎上進行改進即可滿足瀏覽器端的需要,既然瀏覽器端需要function包裝,需要非同步載入,那麼新增一個方案,能把現有模組轉化為適合瀏覽器端的就行了,有點像“保皇派”。基於這個主張,制定了Modules/Transport(http://wiki.commonjs.org/wiki/Modules/Transport)規範,提出了先通過工具把現有模組轉化為複合瀏覽器上使用的模組,然後再使用的方案。 browserify就是這樣一個工具,可以把nodejs的模組編譯成瀏覽器可用的模組。(Modules/Transport規範晦澀難懂,我也不確定browserify跟它是何關聯,有知道的朋友可以講一下) 目前的最新版是Modules/1.1.1(
http://wiki.commonjs.org/wiki/Modules/1.1.1
),增加了一些require的屬性,以及模組內增加module變數來描述模組資訊,變動不大。  2. Modules/Async派 這一波人有點像“革新派”,他們認為瀏覽器與伺服器環境差別太大,不能沿用舊的模組標準。既然瀏覽器必須非同步載入程式碼,那麼模組在定義的時候就必須指明所依賴的模組,然後把本模組的程式碼寫在回撥函式裡。模組的載入也是通過下載-回撥這樣的過程來進行,這個思想就是AMD的基礎,由於“革新派”與“保皇派”的思想無法達成一致,最終從CommonJs中分裂了出去,獨立制定了瀏覽器端的js模組化規範AMD(Asynchronous Module Definition)(
https://github.com/amdjs/amdjs-api/wiki/AMD
) 本文後續會繼續討論AMD規範的內容。  3. Modules/2.0派 這一波人有點像“中間派”,既不想丟掉舊的規範,也不想像AMD那樣推到重來。他們認為,Modules/1.0固然不適合瀏覽器,但它裡面的一些理念還是很好的,(如通過require來宣告依賴),新的規範應該相容這些,AMD規範也有它好的地方(例如模組的預先載入以及通過return可以暴漏任意型別的資料,而不是像commonjs那樣exports只能為object),也應採納。最終他們制定了一個Modules/Wrappings(http://wiki.commonjs.org/wiki/Modules/Wrappings)規範,此規範指出了一個模組應該如何“包裝”,包含以下內容:
1. 全域性有一個module變數,用來定義模組 2. 通過module.declare方法來定義一個模組 3. module.declare方法只接收一個引數,那就是模組的factory,次factory可以是函式也可以是物件,如果是物件,那麼模組輸出就是此物件。 4. 模組的factory函式傳入三個引數:require,exports,module,用來引入其他依賴和匯出本模組API 5. 如果factory函式最後明確寫有return資料(js函式中不寫return預設返回undefined),那麼return的內容即為模組的輸出。
使用該規範的例子看起來像這樣:
//可以使用exprots來對外暴漏API
module.declare(function(require, exports, module)
{
    exports.foo = "bar";
});
//也可以直接return來對外暴漏資料
module.declare(function(require)
{
return { foo: "bar" };
});

AMD/RequireJs的崛起與妥協

AMD的思想正如其名,非同步載入所需的模組,然後在回撥函式中執行主邏輯。這正是我們在瀏覽器端開發所習慣了的方式,其作者親自實現了符合AMD規範的requirejs,AMD/RequireJs迅速被廣大開發者所接受。 AMD規範包含以下內容:
1. 用全域性函式define來定義模組,用法為:define(id?, dependencies?, factory); 2. id為模組標識,遵從CommonJS Module Identifiers規範 3. dependencies為依賴的模組陣列,在factory中需傳入形參與之一一對應 4. 如果dependencies的值中有"require"、"exports"或"module",則與commonjs中的實現保持一致 5. 如果dependencies省略不寫,則預設為["require", "exports", "module"],factory中也會預設傳入require,exports,module 6. 如果factory為函式,模組對外暴漏API的方法有三種:return任意型別的資料、exports.xxx=xxx、module.exports=xxx 7. 如果factory為物件,則該物件即為模組的返回值
基於以上幾點基本規範,我們便可以用這樣的方式來進行模組化組織程式碼了:
//a.js
define(function(){
     console.log('a.js執行');
     return {
          hello: function(){
               console.log('hello, a.js');
          }
     }
});
//b.js
define(function(){
     console.log('b.js執行');
     return {
          hello: function(){
               console.log('hello, b.js');
          }
     }
});
//main.js
require(['a', 'b'], function(a, b){
     console.log('main.js執行');
     a.hello();
     $('#b').click(function(){
          b.hello();
     });
})
上面的main.js被執行的時候,會有如下的輸出: a.js執行
b.js執行
main.js執行
hello, a.js 在點選按鈕後,會輸出: hello, b.js 這結局,如你所願嗎?大體來看,是沒什麼問題的,因為你要的兩個hello方法都正確的執行了。 但是如果細細來看,b.js被預先載入並且預先執行了,(第二行輸出),b.hello這個方法是在點選了按鈕之後才會執行,如果使用者壓根就沒點,那麼b.js中的程式碼應不應該執行呢? 這其實也是AMD/RequireJs被吐槽的一點,預先下載沒什麼爭議,由於瀏覽器的環境特點,被依賴的模組肯定要預先下載的。問題在於,是否需要預先執行?如果一個模組依賴了十個其他模組,那麼在本模組的程式碼執行之前,要先把其他十個模組的程式碼都執行一遍,不管這些模組是不是馬上會被用到。這個效能消耗是不容忽視的。 另一點被吐槽的是,在定義模組的時候,要把所有依賴模組都羅列一遍,而且還要在factory中作為形參傳進去,要寫兩遍很大一串模組名稱,像這樣:
define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){  ..... })
編碼過程略有不爽。 好的一點是,AMD保留了commonjs中的require、exprots、module這三個功能(上面提到的第4條)。你也可以不把依賴羅列在dependencies陣列中。而是在程式碼中用require來引入,如下:
define(function(){
     console.log('main2.js執行');

     require(['a'], function(a){
          a.hello();    
     });

     $('#b').click(function(){
          require(['b'], function(b){
               b.hello();
          });
     });
});
我們在define的引數中未寫明依賴,那麼main2.js在執行的時候,就不會預先載入a.js和b.js,只是執行到require語句的時候才會去載入,上述程式碼的輸出如下: main2.js執行
a.js執行
hello, a.js 可以看到b.js並未執行,從網路請求中看,b.js也並未被下載。只有在按鈕被點選的時候b.js才會被下載執行,並且在回撥函式中執行模組中的方法。這就是名副其實的“懶載入”了。 這樣的懶載入無疑會大大減輕初始化時的損耗(下載和執行都被省去了),但是弊端也是顯而易見的,在後續執行a.hello和b.hello時,必須得實時下載程式碼然後在回撥中才能執行,這樣的使用者體驗是不好的,使用者的操作會有明顯的延遲卡頓。 但這樣的現實並非是無法接受的,畢竟是瀏覽器環境,我們已經習慣了操作網頁時伴隨的各種loading。。。 但是話說過來,有沒有更好的方法來處理問題呢?資源的下載階段還是預先進行,資源執行階段後置,等到需要的時候再執行。這樣一種折衷的方式,能夠融合前面兩種方式的優點,而又迴避了缺點。 這就是Modules/Wrappings規範,還記得前面提到的“中間派”嗎? 在AMD的陣營中,也有一部分人提出這樣的觀點,程式碼裡寫一堆回撥實在是太噁心了,他們更喜歡這樣來使用模組:
var a = require('a');
a.hello();

$('#b').click(function(){
        var b = require('b');
        b.hello();
});
於是,AMD也終於決定作妥協,相容Modules/Wrappings的寫法,但只是部分相容,例如並沒有使用module.declare來定義模組,而還是用define,模組的執行時機也沒有改變,依舊是預先執行。因此,AMD將此相容稱為Simplified CommonJS wrapping,即並不是完整的實現Modules/Wrappings。 作了此相容後,使用requirejs就可以這麼寫程式碼了:
//d.js
define(function(require, exports, module){
     console.log('d.js執行');
     return {
          helloA: function(){
               var a = require('a');
               a.hello();
          },
          run: function(){
               $('#b').click(function(){
                    var b = require('b');
                    b.hello();
               });
          }
     }
});
注意定義模組時候的輕微差異,dependencies陣列為空,但是factory函式的形參必須手工寫上require,exports,module,(這不同於之前的dependencies和factory形參全不寫),這樣寫即可使用Simplified CommonJS wrapping風格,與commonjs的格式一致了。 雖然使用上看起來簡單,然而在理解上卻給後人埋下了一個大坑。因為AMD只是支援了這樣的語法,而並沒有真正實現模組的延後執行。什麼意思呢?上面的程式碼,正常來講應該是預先下載a.js和b.js,然後在執行模組的helloA方法的時候開始執行a.js裡面的程式碼,在點選按鈕的時候開始執行b.js中的方法。實際卻不是這樣,只要此模組被別的模組引入,a.js和b.js中的程式碼還是被預先執行了。 我們把上面的程式碼命名為d.js,在別的地方使用它:
require(['d'], function(d){
   
});
上面的程式碼會輸出 a.js執行
b.js執行
d.js執行 可以看出,儘管還未呼叫d模組的API,裡面所依賴的a.js和b.js中的程式碼已經執行了。AMD的這種只實現語法卻未真正實現功能的做法容易給人造成理解上的困難,被強烈吐槽

相容幷包的CMD/seajs

既然requirejs有上述種種不甚優雅的地方,所以必然會有新東西來完善它,這就是後起之秀seajs,seajs的作者是國內大牛淘寶前端佈道者玉伯。seajs全面擁抱Modules/Wrappings規範,不用requirejs那樣回撥的方式來編寫模組。而它也不是完全按照Modules/Wrappings規範,seajs並沒有使用declare來定義模組,而是使用和requirejs一樣的define,或許作者本人更喜歡這個名字吧。(然而這或多或少又會給人們造成理解上的混淆),用seajs定義模組的寫法如下:
//a.js
define(function(require, exports, module){
     console.log('a.js執行');
     return {
          hello: function(){
               console.log('hello, a.js');
          }
     }
});
//b.js
define(function(require, exports, module){
     console.log('b.js執行');
     return {
          hello: function(){
               console.log('hello, b.js');
          }
     }
});
//main.js
define(function(require, exports, module){
     console.log('main.js執行');

     var a = require('a');
     a.hello();    

     $('#b').click(function(){
          var b = require('b');
          b.hello();
     });
    
});
定義模組時無需羅列依賴陣列,在factory函式中需傳入形參require,exports,module,然後它會呼叫factory函式的toString方法,對函式的內容進行正則匹配,通過匹配到的require語句來分析依賴,這樣就真正實現了commonjs風格的程式碼。 上面的main.js執行會輸出如下: main.js執行
a.js執行
hello, a.js a.js和b.js都會預先下載,但是b.js中的程式碼卻沒有執行,因為還沒有點選按鈕。當點選按鈕的時候,會輸出如下: b.js執行
hello, b.js 可以看到b.js中的程式碼此時才執行。這樣就真正實現了“就近書寫,延遲執行“,不可謂不優雅。 如果你一定要挑出一點不爽的話,那就是b.js的預先下載了。你可能不太想一開始就下載好所有的資源,希望像requirejs那樣,等點選按鈕的時候再開始下載b.js。本著相容幷包的思想,seajs也實現了這一功能,提供require.async API,在點選按鈕的時候,只需這樣寫:
var b = require.async('b');
b.hello();
b.js就不會在一開始的時候就載入了。這個API可以說是簡單漂亮。 關於模組對外暴漏API的方式,seajs也是融合了各家之長,支援commonjs的exports.xxx = xxx和module.exports = xxx的寫法,也支援AMD的return寫法,暴露的API可以是任意型別。 你可能會覺得seajs無非就是一個抄,把別人家的優點都抄過來組合了一下。其實不然,seajs是commonjs規範在瀏覽器端的踐行者,對於requirejs的優點也加以吸收。看人家的名字,就是海納百川之意。(再論起名的重要性~),既然它的思想是海納百川,討論是不是抄就沒意義了。 鑑於seajs融合了太多的東西,已經無法說它遵循哪個規範了,所以玉伯乾脆就自立門戶,起名曰CMD(Common Module Definition)規範,有了綱領,就不會再存在非議了。

面向未來的ES6模組標準

既然模組化開發的呼聲這麼高,作為官方的ECMA必然要有所行動,js模組很早就列入草案,終於在2015年6月份釋出了ES6正式版。然而,可能由於所涉及的技術還未成熟,ES6移除了關於模組如何載入/執行的內容,只保留了定義、引入模組的語法。所以說現在的ES6 Module還只是個雛形,半成品都算不上。但是這並不妨礙我們先窺探一下ES6模組標準。 定義一個模組不需要專門的工作,因為一個模組的作用就是對外提供API,所以只需用exoprt匯出就可以了:
//方式一, a.js
export var a = 1;
export var obj = {name: 'abc', age: 20};
export function run(){....}
//方式二, b.js
var a = 1;
var obj = {name: 'abc', age: 20};
function run(){....}
export {a, obj, run}

相關推薦

js模組歷程

服務端向前端進軍 Modules/1.0規範源於服務端,無法直接用於瀏覽器端,原因表現為: 1. 外層沒有function包裹,變數全暴漏在全域性。如上面例子中increment.js中的add。 2. 資源的載入方式與服務端完全不同。服務端require一個模組,直接就從硬碟或者記憶體中讀取了

js-模組開發總結

一.模組開發的概念 模組化開發是什麼:模組化開發是一種生產方式,這種方式生產效率高,維護成本低。從軟體開發的角度說,模組化開發是一種開發模式,寫程式碼的一種方式,開發效率高,維護成本低。 為什麼需要模組化開發:當一個專案開發的越來越複雜的時候,會遇到一些問題,比如命名衝突(重新命名),檔

JS模組(Module模式模組,SeaJS工具模組)

1、Module模式模組化 Module模式具有模組化,可重用的基本特徵,封裝了變數和function,只暴露可用public的方法,其它私有方法全部隱藏。在沒有使用模組化工具的情況下,用模組化的思想來編寫整個JS結構。 例如下圖,以webrtcUI層程式碼為例,MeetingMainPag

require-js-模組 CMD AMD

模組化的標準有了模組,我們就可以更方便地使用別人的程式碼,想要什麼功能,就載入什麼模組。這樣做有一個前提,那就是大家必須以同樣的方式編寫模組,否則你有你的寫法,我有我的寫法,豈不是亂了套!  CommonJS:是一個模組化的標準,Node.js在使用的模組化標準。適用與後端開發的標準。AMD(As

點選按鈕刪除bootstrapTable選中行,js模組及一些問題的總結

頁面效果展示 html程式碼: <div class="col-md-12" style="height: 15%"> <form action="web?module=stwmgr&action=Develop&method=se

JS模組(一):CommonJS

模組化的規範:         目前有四種:             1.CommonJS      &nbs

node.js模組思想初探

系統模組(核心模組):node本身自帶,可以直接require的模組 自定義模組:自己寫的,以及在npm上下載的模組 一部分常用的系統模組的作用(印象) Crypto 加密 Events 事件 Net 網路操作 OS 作業系統資訊 Path 處理檔案路徑 S

【requirejs】JS模組工具requirejs教程

初識requirejs 隨著網站功能逐漸豐富,網頁中的js也變得越來越複雜和臃腫,原有通過script標籤來匯入一個個的js檔案這種方式已經不能滿足現在網際網路開發模式,我們需要團隊協作、模組複用、單元測試等等一系列複雜的需求。 RequireJS是一個非常小巧的JavaScript模組載入框架,是A

JS模組開發規範

JS模組化開發規範,以下介紹三種 commonJS規範(Nodejs模組系統遵循此規範,適用於服務端) 1、 規範定義 CommonJS規範規定,一個檔案就是一個模組,用module變數代表當前模組。 Node在其內部提供一個Module的構建函式。所有模組都是Module的例項 2、 module.ex

一覽js模組:從CommonJS到ES6

本文由雲+社群發表 模組化是指把一個複雜的系統分解到一個一個的模組。 模組化開發的優點: (1)程式碼複用,讓我們更方便地進行程式碼管理、同時也便於後面程式碼的修改和維護。 (2)一個單獨的檔案就是一個模組,是一個單獨的作用域,只向外暴露特定的變數和函式。這樣可以避免汙染全域性變數,減少變數

JS模組程式設計之AMD模型實現原理(Requirejs瀏覽器模型,nodejs伺服器模型)

官方引數手冊:https://requirejs.org/docs/api.html     /** * require使用指南! * 第一步:建立一個符合Require CMD模組化的標準目錄結構,去官方檢視! * 第二步:在html檔案中指定主js檔

JS模組之歷史演進

為什麼迫切需要js模組化 隨著2006年ajax概念被提出,前端的業務的業務越來越多,程式碼也越來越多,並逐漸發展成為前後端分離的狀態,出現了專門的前端攻城獅。 但是隨著程式碼的增多,也隨之衍生很多問題。 1.早期的js程式碼: common.js let mo

js 模組規範(commonjs、AMD、ES6、CMD)

開發中最流行的 commonjs、AMD、ES6、CMD 規範。 參考資料: https://mp.weixin.qq.com/s/MPEhWlS9KiIc9I6Of5GpOQ http://www.ruanyifeng.com/blog/2015/05/commonjs-in-

JS模組程式設計總結

一、背景 隨著的網際網路技術的不斷髮展,瀏覽器逐漸成了集大成的CS客戶端,頁面功能在向系統級、軟體級靠攏的趨勢下,開發團隊也需要一些軟體工程的方法來開發WEB專案,如測試驅動、面向物件,而模組化程式設計更是成為一種急需應用的技術。 二、原因 為方便檔案管理、增加複用

js模組程序

模組化程序 模組就是實現特定功能的一組方法。 1、無模組時代:通過script標籤來匯入一個個的js檔案,而一個js檔案中,程式碼簡單的堆在一起,把不同的函式簡單地放在一起,就算是一個模組,只要能從上往下依次執行就可以了。帶來的問題是:全域性變數命名衝突、模

[前端]關於JS模組/AMD/CMD/UMD及CSS的BEM

工作上接觸到的模組化都是比較主流的ESM和commonJS CSS上通用的是BEM 這裡主要是總結給自己看的 ESM(ES6 Module) 一個檔案一個模組 基本是webpack結合vue最常用的東西了 引入import,暴露用export import re

JS模組規範:AMD/CMD/CommonJS

一、模組化規範的由來 隨著網頁的複雜化,javascript程式碼也相應變得龐大起來,程式碼之間的分工合作就尤為重要了,這時候有了模組,我們就可以只關注自己業務的核心邏輯,其他想要實現的功能直接載入他人的模組便足夠了。考慮到模組的統一,便有了模組規範化。接下來

JS 模組規範

在我們最初寫程式碼的時候,引入JS檔案用script標籤來引入,並且在引入多個JS檔案時,當前檔案所依賴的JS檔案必須放在前面。也就存在一個順序的問題,而且這是由開發者去判斷和把控的。而現在前端專案越來越複雜,難免會出現很多很多script標籤引入JS,這無論對

JS模組程式設計之AMD規範(一)

隨著網站逐漸變成"網際網路應用程式",嵌入網頁的Javascript程式碼越來越龐大,越來越複雜。 網頁越來越像桌面程式,需要一個團隊分工協作、進度管理、單元測試等等......開發者不得不使用軟體工程的方法,管理網頁的業務邏輯。 JavaScript模組化程式設

JS模組方案(2)之CMD 模組(SeaJS)方案

一、 CMD 1、基本介紹 SeaJS 是一個適用於 Web 瀏覽器端的模組載入器。使用 SeaJS,可以更好地組織 JavaScript 程式碼。 2、實現庫:seajs 1.3.1 S