自我使用JS這門語言以來,JS在整個發展歷史中不斷的變遷和優化,隨着使用者的增多和瀏覽器的支持規範的發展,JS的發展大體我劃分了六個階段。 表單校驗 --> 工具類庫 --> 組件庫 --> 前端框架 --> 前端應用 --> 前端微服務javascript
前端模塊化的發展是把 函數 做爲第一步的:模塊的本質就是實現特定功能的一組方法,在JavaScript中,函數是建立局部做用域的惟一方式,所以採用了函數做爲JS模塊化的第一步。html
function cut(){
//具體實現
}
複製代碼
Cut
方法能夠做爲模塊被直接調用,這樣的好處是實現了代碼的複用,缺點是很容易形成命名衝突,而且污染了全局變量。當頁面抽離出的函數模塊較多時,看不出它們之間的組織關係。前端
把 函數掛載到對象 上是前端模塊化邁出的第二步。把功能函數掛載到一個對象上,有須要的時候,直接經過調用對象屬性的方式進行函數調用。 這裏隱隱有股Java類的感受,學過Java的可能會感受熟悉,本質上就是namespace模式的使用。C++的應該很瞭解。java
const FileUnit= {
path:undefined,
read: function(){},
write: function(){}
}
FileUnit.read(path);
複製代碼
FileUnit
對象掛載了模塊上的成員函數和變量。在進行使用的時候,就是調用這個對象的屬性。這樣的寫法解決了第一步的問題,可是,由此衍生出了新的問題,對象會暴露出全部的成員變量和函數,而且掛載的函數和變量有可能被改寫。json
當即執行函數(IIFE) 是爲了解決暴露對象而邁出的第三步。後端
//什麼是當即執行函數?-->就是聲明完成馬上執行的函數
function x(){} //聲明函數x
x();//調用函數x
//IIFE 聲明完成馬上調用 (fn(){})()
const module= (function(){
let _path = "./xxx.json";
let cut = function(){
console.log(_private)
}
return {
cut: cut
}
})()
module.cut();
module._path; // undefined
複製代碼
當即執行函數(IIFE) 容許在函數內部使用局部變量,而不會意外覆蓋同名全局變量,但仍然可以訪問到全局變量,而在模塊外部沒法修改未暴露的私有變量和函數。但缺點也很明顯,不少時候,咱們的模塊都是架設在別的模塊的依賴上進行封裝,那當這個module模塊依賴另外一個模塊怎麼辦?在進行探索以後,前端模塊化邁出了第四步。數組
引入依賴瀏覽器
const module= (function(window,$){
let cut = function(){
console.log(window,$)
}
return {
cut: cut
}
})(window,jQuery)
複製代碼
將要引入的依賴做爲匿名函數的參數進行傳入,由此就能夠在函數內部訪問到依賴的下級模塊進行邏輯的封裝,由此開始,前端模塊化正式開始了漫漫長路。也由此,前端模塊化題提出了幾個不得不解決的問題:緩存
前端的工程師對於上述問題給出的全部解決方案都有一個共同點:使用單個全局變量來把全部的代碼包含在一個函數內,由此來建立私有的命名空間和閉包做用域。由此,前端模塊規範誕生了。安全
CommonJS 規範爲Javascript制定了一個美好的願景 ——— Javascript可以在任何地方運行。
在Web的發展過程當中,前端的規範化在穩步推行,但後端的JS規範卻遠遠落後,對後端的JS自身而言,還有不少缺陷
CommonJS 規範的提出,主要是爲了彌補後端JS沒有標準的缺陷,但願JS具備開發相似Java或者Python的大型應用的基礎能力。
CommonJS 對模塊的定義分爲三個部分
//模塊引用
const math=require('math')
複製代碼
require
方法接收一個 模塊標識 ,以此引入一個模塊的API到上下文環境中。
//模塊定義
exports.add =function(a,b){
return a+b
}
//module.exports = value
//exports.xxx = value
複製代碼
CommonJS規範規定,一個文件就是一個模塊。每一個模塊內部,module變量表明當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,實際上是加載該模塊的module.exports屬性。
//模塊標識
模塊標識就是傳遞給require()方法的參數。參數必須是符合小駝峯命名的字符串,或者以./ .. 開頭的相對路徑或絕對路徑,能夠省略js後綴。
複製代碼
CommonJS模塊定義簡單,接口簡潔,成功的將類聚的方法和變量等限定在私有做用域的中,同時支持導入和導出功能以順暢的鏈接上下游依賴。
在服務器端,CommonJS模塊的加載是同步的,而且模塊加載的順序,按照其在代碼中出現的順序加載,模塊能夠屢次加載,可是隻會在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。和其餘模塊加載機制有重大不一樣的是CommonJS模塊的加載輸入的是被輸出的值的拷貝。也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。這點與ES6模塊化有重大差別。
// lib.js
let counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
const lib = require('./lib');
console.log(lib.counter); // 3
lib.incCounter();
console.log(lib.counter); // 3
複製代碼
counter輸出之後,lib.js模塊內部的變化就影響不到counter了。這是由於counter是一個原始類型的值,會被緩存。除非寫成一個函數,才能獲得內部變更後的值。
CommonJS模塊規範的推出使得服務端Js開始模塊化,可是CommonJS規範對瀏覽器端並不適用,CommonJS模塊的加載是同步加載,而且按照其在代碼中出現的順序加載,瀏覽器端的模塊都存放在服務器上,加載模塊的等待時間取決於網速快慢,若是網速較差,會致使瀏覽器處於"假死"狀態。所以,瀏覽器端的模塊,不能採用"同步加載"(synchronous),只能採用"異步加載"(asynchronous)。這就是AMD規範誕生的背景。()
AMD 即Asynchronous Module Definition,中文名是異步模塊定義的意思。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。
AMD一開始是CommonJS規範中的一個草案,全稱是Asynchronous Module Definition,即異步模塊加載機制。後來由該草案的做者以RequireJS實現了AMD規範,因此通常說AMD也是指RequireJS。AMD規範的實現源於想要一種比當今「編寫一堆必須手動排序的具備隱式依賴項的腳本標籤」更好的模塊依賴解決方案而產生,AMD模塊但願能夠在瀏覽器中直接使用,而且具備良好的調試性且不依賴與特定化的服務器。AMD規範設計源於Dojo使用XHR + eval的真實經驗。
RequireJS的基本思想是,經過define方法,將代碼定義爲模塊;經過require方法,實現代碼的模塊加載。
//定義沒有依賴的模塊
define(id?, dependencies?, factory);
//id :可選參數,它指的是模塊的名字。
//dependencies:可選參數,定義中模塊所依賴模塊的數組。
//factory:模塊初始化要執行的函數或對象
//
define("alpha", ["require", "exports", "module"], function (require, exports, module) {
//三種暴露API的方式
// exports.xxx=xxx
// module.exports=xxx
// return xxx;
});
//模塊引入
require([module], callback);
//module:一個數組,裏面的成員就是要加載的模塊.
//callback:模塊加載成功以後的回調函數。
require(["a","b","c"],function(a,b,c){
//Code
});
複製代碼
從整體上說,AMD規範修正了不少CMD規範的細節問題,並在模塊化的路子上進行了並行的邁進。這裏給出一個我以爲講的不錯的AMD規範的文章:Why AMD ? 有興趣的能夠讀一讀詳細的瞭解AMD規範作了哪些改進。
CMD 即Common Module Definition, CMD是sea.js的做者在推廣sea.js時提出的一種規範.SeaJS與RequireJS並稱,SeaJS做者爲阿里的玉伯。CMD規範專門用於瀏覽器端,模塊的加載是異步的,模塊使用時纔會加載執行。CMD規範整合了CommonJS和AMD規範的特色。在 Sea.js 中,全部 JavaScript 模塊都遵循 CMD模塊定義規範。
在CMD規範中,一個模塊就是一個文件。代碼的書寫格式以下:
define(function(require, exports, module) {
// 模塊代碼
// 使用require獲取依賴模塊的接口
// 使用exports或者module或者return來暴露該模塊的對外接口
})
複製代碼
CMD規範採用全局的define
函數定義模塊, 無需羅列依賴數組,在factory
函數中需傳入形參require,exports,module
. require
用來加載一個 js 文件模塊和獲取指定模塊的接口對象module.exports
。
Sea.js
加載依賴的方式分爲兩個時期:
那做爲異步加載模塊的兩種規範,AMD和CMD各有千秋。
上述提到的模塊規範的都不屬於JS原生支持的, 在ECMAScript 6 (ES6)中,引入了模塊功能, ES6 的模塊功能汲取了CommonJS 和 AMD 的優勢,擁有簡潔的語法並支持異步加載,而且還有其餘諸多更好的支持。
ES6 模塊的設計思想就是:一個 JS 文件就表明一個 JS 模塊。在模塊中你可使用 import 和 export 關鍵字來導入或導出模塊中的東西。
ES6 模塊主要具有如下幾個基本特色:
/** 定義模塊 math.js **/
let basicNum = 0;
let add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模塊 **/
import { basicNum, add } from './math';
function test(item) {
item.textContent = add(99 + basicNum);
}
複製代碼
上述的例子採用了math.js
定義了模塊,經過export
導出了一個掛在了basicNum, add
兩個函數的對象。在進行引用的時候,經過對對象進行析構獲得導出的對應函數。這種加載方式,使得在採用import
命令進行導入的時候,用戶須要知道所要加載的變量名或函數名,不然沒法加載出對應的函數。爲了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default
命令,爲模塊指定默認輸出。
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
複製代碼
這種導入方式使得用戶能夠爲匿名函數指定任意名字進行使用。
① CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
② CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
-運行時加載:commonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,而後再從這個對象上讀取方法,這種加載稱爲「運行時加載」。commonJS腳本代碼在require的時候,就會所有執行。一旦出現某個模板被「循環加載」,就只能輸出已經執行的部分,還未執行的部分不會輸出。
③ ES6 模塊的運行機制與 CommonJS 不同。ES6 模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。
本片簡述了JS的一路發展以來,模塊化的歷程,從最開始模塊化探索,到橫空出世的CommonJS規範,從草案開始的AMD規範到Sea.js推廣的CMD規範,最後官方的ES6模塊化爲模塊化立下了一個階段的里程碑。下篇將會回到NodejS談談Node中模塊化和核心模塊。 由於筆者水平有限,若是有錯誤或者遺漏,歡迎留言進行建議。