大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以點此加我微信ruochuan12?進群參與,每周大家一起學習200行左右的源碼,共同進步。已進行三個月了,很多小伙伴表示收獲頗豐。
一、 為什么需要模塊化
以前沒有模塊化時,我們可能會按如下方式劃分模塊:
通過 <script> 標簽引入各個文件,把每個文件看成是一個模塊,每個模塊的接口通常是暴露在全局作用域下的,也就是定義在 window 對象中。
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="module3.js"></script>
如果通過這種方式做模塊化,當項目變得越來越大時,很容易造成全局變量沖突,項目也會變得越來越難以管理。
因此我們需要找到一種合適的方式來幫助我們模塊化,模塊化能給我們帶來哪些好處呢?具體來說,主要包含以下幾個方面:
減少全局變量污染
控制依賴
增強代碼的可維護性
增加代碼的復用性
分治思想的實踐
二、模塊化標準現狀
1. CommonJS
規范
模塊的標識應遵循一定的書寫規則。
定義全局函數?
require(dependency)
,通過傳入模塊標識來引入其他依賴模塊,執行的結果即為別的模塊暴漏出來的 API。如果被 require 函數引入的模塊中也包含外部依賴,則依次加載這些依賴。
如果引入模塊失敗,那么 require 函數應該拋出一個異常。
模塊通過變量 exports 來向外暴露 API,exports 只能是一個 Object 對象,暴露的 API 須作為該對象的屬性。
使用方式
導出模塊:
// math.js
var num = 0;
function add(a, b) {return a + b;
}
module.exports = {num: num,add: add
}
加載模塊:
// 引入自定義的模塊時,參數包含路徑,可省略.js
// 引入核心模塊時,不需要帶路徑,如var http = require("http");
var math = require('./math');
math.add(1, 2) //3
優點
簡單易用。
解決了模塊依賴的問題。
減少了全局變量污染。
缺點
無法在瀏覽器端使用。
無法非阻塞的并行加載多個模塊。
2. AMD(Async Module Definition)
代表作 RequireJS。
規范
模塊的標識遵循 CommonJS Module Identifiers。
定義全局函數?
define(id, dependencies, factory)
,用于定義模塊。dependencies 為依賴的模塊數組,在 factory 中需傳入形參與之一一對應。如果 dependencies 的值中有 require、exports 或 module,則與 CommonJS 中的實現保持一致。
如果 dependencies 省略不寫,則默認為 ['require', 'exports', 'module'],factory 中也會默認傳入三者。
如果 factory 為函數,模塊可以通過以下三種方式對外暴漏 API:return 任意類型;
exports.XModule = XModule、module.exports = XModule
。如果 factory 為對象,則該對象即為模塊的導出值。
使用方式
定義模塊:
獨立模塊
define({method1: function() {},method2: function() {},
});// 函數的返回值就是輸出的模塊
define(function () {return {method1: function() {},method2: function() {},};
});
有依賴的模塊
define(['module1', 'module2'], function(m1, m2) {
...
});
// module1模塊和module2模塊指的是,當前目錄下的module1.js文件和module2.js文件,等同于寫成['./module1', './module2']
需要注意的是,回調函數必須返回一個對象,這個對象就是你定義的模塊。
調用模塊:
require(['foo', 'bar'], function ( foo, bar ) {foo.doSomething();
});
優點
可以用于瀏覽器。
異步加載模塊。
可以并行加載多個模塊。
缺點
提高了開發成本。
不能按需加載,而是提前加載所有的依賴。
* RequireJS 從 2.0 開始,也改成了可以延遲執行。
3. CMD (Common Module Definition)
CMD 是?sea.js
?在推廣過程中對模塊定義的規范化產出,屬于 CommonJS 的一種規范。
使用方式
定義模塊:
define(function (require, exports, module) {var add = function (a, b) {return a + b;}exports.add = add;
})
使用模塊:
seajs.use(['math.js'], function (math) {var sum = math.add(1, 2);
});
優點
實現了瀏覽器端的模塊化加載。
可以按需加載。
依賴就近,延遲執行。
缺點
依賴 SPM 打包,模塊加載邏輯偏重。
4. UMD (Universal Module Definition)
UMD 是一種 JavaScript 通用模塊定義規范,讓你的模塊能在 JavaScript 所有運行環境中發揮作用。
規定如下:
優先判斷是否存在 exports 方法,如果存在,則采用 CommonJS 方式加載模塊;
其次判斷是否存在 define 方法,如果存在,則采用 AMD 方式加載模塊;
最后判斷 global 對象上是否定義了所需依賴,如果存在,則直接使用;反之,則拋出異常。
5. ES Module
使用方式
導出模塊:
// 導出
exportfunction hello() { };
exportdefault {// ...
};
引入模塊:
import { readFile } from'fs';
import React from'react';
優點
語法層面的支持,使用簡單。
缺點
瀏覽器還沒有完全兼容,必須通過工具轉換成標準的 ES5 后才能正常運行。
瀏覽器的支持情況
三、模塊化的演變歷史
2009 年的時候,Mozilla 的工程師 Kevin Dangoor 與同事們一起制定了一套 JsaveScript 模塊化標準,并取名為 ServerJS。ServerJS 最早是用于服務端的,目的是為了在自動化測試的工作中提供模塊化導入的功能。之后 ServerJS 更名為了 CommonJS。
同年 9 月,Ryan Dahl 創造了 Node.js,而一開始 Node.js 還沒有包管理工具,隨后采用 CommonJS 規范的 npm (即 node package manager) 誕生了。隨著 Node.js 的快速發展,CommonJS 規范也漸漸進入廣大前端開發者的視野。至此,JavaScript 第一個模塊化規范,也正式登入歷史舞臺。
隨著 npm 的流行,廣大前端開發者也希望引入這種模塊化的方案到日常的開發工作中。但是 CommonJS 只能應用于服務端,因此勢必需要重新制定規范標準。而此時,關于如何制定新的標準,主要有三大流派:
保守派
CommonJS 已經在服務端成功應用了,那么在瀏覽器加載模塊前,我們先通過工具將模塊轉換成瀏覽器能夠執行的代碼就可以了。該想法與如今的 babel 等工具思路是相似的,通過將高版本的代碼轉換為低版本的代碼,目的都是為了兼容。而 browserify 就是這一觀點下的產物。
激進派
瀏覽器與服務端存在很大的差異,我們應該根據瀏覽器的特點,放棄 require 的方式而是使用回調的方式引入模塊。將同步加載模塊更改為異步加載模塊。
中間派
CommonJS 中的 require 等規范還是有可取之處,在盡可能與現有的 CommonJS 規范保持一致的前提下,我們也可以引入一些好的特性,比如 exports 可以導出更多類型而不是局限于 Object。
各自進展
激進派
激進派的 James Burke 在 2009 年開發出了 RequireJS 模塊加載器。2011 年,在 RequireJS 社區的基礎上,誕生了 AMD(Async Module Definition)社區。AMD 是第一個支持瀏覽器端的 Javascript 模塊化解決方案,RequireJS 迅速被廣大開發者熟知和采用。
中間派
中間派的故事比較曲折。CommonJS 的主要貢獻者之一 Wes Garland 給出了一個名為 BravoJS 的實現。Wes Garland 本人是學院派,理論功底十分強,但寫出的作品卻不很實用。另一位實戰派大佬提出了 Modules/Wrappings 的方案,并給出了一個名為 FlyScript 的實現。而兩位大佬對具體的實現發生了一些爭論,最后以 FlyScript 的 GitHub 倉庫被刪除而結束這段恩怨。
2011 年 4 月,阿里巴巴的前端大佬玉伯,因為給 RequireJS 提出的建議被不斷拒絕后,在參考了 AMD 和 CommonJS 的方案后,便自己寫了一個模塊加載器 Sea.JS,同時提出了 CMD 規范。CMD 規范的主要內容與 AMD 相似,但是保留了 CommonJS 中延遲加載和就近聲明的特性。
UMD
2014 年 9 月,美籍華裔 Homa Wong 提交了 UMD 第一個版本的代碼。UMD 即 Universal Module Definition 的縮寫,它本質上并不是一個真正的模塊化方案,而是將 CommonJS 和 AMD 相結合。
ES Module
2016 年 5 月,經過了兩年的討論,ECMAScript 6.0 終于正式通過決議,成為了國際標準。在這一標準中,首次引入了 import 和 export 兩個 JavaScript 關鍵字,并提供了被稱為 ES Module 的模塊化方案。2017 年 9 月, Chrome 的 61.0 版本首次在瀏覽器端支持了 ES Module。目前已經有很多瀏覽器原生支持了 ES Module。而伴隨著 ES Module 的興起,也出現了像 Vite 這樣的構建工具。
最近組建了一個湖南人的前端交流群,如果你是湖南人可以加我微信?ruochuan12?私信 湖南 拉你進群。
推薦閱讀
1個月,200+人,一起讀了4周源碼
我歷時3年才寫了10余篇源碼文章,但收獲了100w+閱讀
老姚淺談:怎么學JavaScript?
我在阿里招前端,該怎么幫你(可進面試群)
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》10余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助1000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。分享、收藏、點贊、在看我的文章就是對我最大的支持~