模塊是什么呢?
從Javascript的發(fā)展史來看,模塊系統(tǒng)的出現(xiàn),是一種對(duì)隔絕全局作用域,關(guān)注點(diǎn)分離,顯式依賴定義,高內(nèi)聚、低耦合的結(jié)構(gòu)的追求。
模塊化?
主要解決代碼分割、作用域隔離、模塊之間的依賴管理以及發(fā)布到生產(chǎn)環(huán)境時(shí)的自動(dòng)打包與處理等多個(gè)方面
背景以及問題
在模塊化思維席卷前端之前,早期的代碼或是集中在一個(gè)文件里,或是物理分散在多個(gè)文件里,但對(duì)作用域并無隔離,他們皆可以接觸到全局作用域中的變量,也可以輕而易舉、甚至是在不知情的情況下就將自己的變量與方法暴露到全局中。此外,不同代碼之間依賴關(guān)系不夠明確,關(guān)聯(lián)代碼的執(zhí)行時(shí)機(jī)難以確定, 缺乏分析與加載機(jī)制。
應(yīng)對(duì)
早期為了解決作用域隔離的問題,很多代碼文件會(huì)將代碼包裹在立即執(zhí)行的匿名函數(shù)中,成功將變量和方法定義等局限在自己的函數(shù)作用域里。
(function() {
? ? //....代碼在這里
})();
后期為了解決前端代碼快速膨脹,js文件加載與執(zhí)行難以時(shí)機(jī)的問題,出現(xiàn)了很多模塊依賴加載的規(guī)范:AMD\CMD\COMMONJS\ES MODULE. 模塊的概念開始清晰并統(tǒng)一起來。
個(gè)人認(rèn)為,關(guān)于模塊化規(guī)范的內(nèi)容,主要聚焦在模塊定義,依賴定義,模塊查找,和模塊導(dǎo)出等。
此處需要詳細(xì)了解規(guī)范,作出修改。
COMMONJS規(guī)范
在CommonJS的規(guī)范中,每個(gè)JavaScript文件就是一個(gè)獨(dú)立的模塊上下文,在這個(gè)上下文中創(chuàng)建的屬性和方法都是私有的。它采用同步加載模塊的策略。該方案的關(guān)鍵字是require與module。
Node.js的module實(shí)現(xiàn)
/*** main.js **/
const a = require('./a.js')
console.log(a)
/*** a.js **/
?module.exports = {
? ? blockName: 'module-a'
}
這里定義一個(gè)a模塊。在執(zhí)行模塊之前,node.js會(huì)使用一個(gè)如下的模塊封裝器將其封裝起來,因此可以隔絕全局作用域,而將欲導(dǎo)出的內(nèi)容掛載到傳入的參數(shù)上,就成功暴露相應(yīng)的內(nèi)容給外部。
(function(exports,require,module,__filename,__dirname){? //....模塊A })
在模塊的上下文中,可以訪問到require、exports、module.exports,__filename、__dirname等。
Node.js的模塊加載后會(huì)緩存,模塊定義腳本只會(huì)執(zhí)行一次, 模塊的依賴與元數(shù)據(jù)也是在執(zhí)行時(shí)慢慢補(bǔ)齊。
var router = require('koa-router')()
console.log('測試',module.children[1],module.children[0])?
const mysql = require('./mysql')
console.log('測試2',module.children[1],module.children[0])?
結(jié)果:?
測試 undefined {//.....module1}
測試2 {//.....module2} {//.....module1}
模塊的查找過程
Node.js有核心模塊,文本文件模塊,和目錄模塊。核心模塊是定義在Node.js源碼lib下的二進(jìn)制模塊,會(huì)被優(yōu)先加載。至于文本文件模塊,如果不是以'./', '../','/'開頭,會(huì)進(jìn)入最近的node_module中查找(支持本地化依賴的關(guān)鍵, 第三方模塊);否則 ,應(yīng)該是相對(duì)模塊的路徑進(jìn)行查找。針對(duì)目錄模塊,Node會(huì)讀入目錄下的package.json來嘗試解讀模塊信息,main定義模塊的入口文件,type定義模塊的類型(commonjs or mudule),exports定義導(dǎo)出。關(guān)于模塊類型,有需要注意的是,現(xiàn)在Node.js推出了實(shí)驗(yàn)性特征,可以在較新的版本支持ES MODULE。項(xiàng)目代碼中以.cjs結(jié)尾的文件是commonjs模塊,而以.mjs結(jié)尾是es模塊。但是注意一點(diǎn),es模塊中不可去調(diào)用commonjs模塊,commonjs模塊不可去調(diào)用es模塊。
循環(huán)依賴
? ? /**? ?a.js:? ?*/
?? ? ? console.log('a開始');
?? ? ? exports.done = false;
?? ? ? const b = require('./b.js');
?? ? ? console.log('在a中,b.done = %j',b.done);
?? ? ? exports.done = true;
?? ? ? console.log('a結(jié)束');
??/**? ? b.js: */
?? ? ? console.log('b開始');
?? ? ? exports.done = false;
?? ? ? const a = require('./a.js');
?? ? ? console.log('在b中,a.done = %j',a.done);
?? ? ? exports.done = true;
?? ? ? console.log(‘b結(jié)束');
? /**? ? main.js: */
?? ? ? console.log('main開始');
?? ? ? const a = require('./a.js');
?? ? ? const b = require('./b.js');
?? ? ? console.log('在main中,a.done=%j,b.done=%j',a.done,b.done);
結(jié)果
? ? ? main開始
? ? ? a開始
? ? ? b開始
? ? ? 在b中,a.done = false
? ? ? b結(jié)束
? ? ? 在a中,b.done = true
? ? ? a結(jié)束
? ? ? 在main中,a.done=true,b.done=true
由于在模塊生成代碼執(zhí)行前,模塊的引用便已存在(模塊封裝器傳入),因?yàn)榭梢钥朔h(huán)模塊依賴。
ES MODULE 規(guī)范
ES Module是語法靜態(tài)的,默認(rèn)使用嚴(yán)格模式。import會(huì)自動(dòng)提升到代碼頂層,意味著在編譯時(shí)確定了輸入和導(dǎo)出,可以更加快的查找依賴,可以使用lint工具對(duì)模塊依賴進(jìn)行檢查。
ES 模塊是異步的。你可以認(rèn)為它是異步的因?yàn)閷?shí)際的運(yùn)作被分成了三個(gè)不同的階段 —— 加載,實(shí)例化以及求值,而這些階段都可以分開完成。
深入ES MODULE,請(qǐng)查看
ES MODULE 和COMMONJS 不同
從使用方面來看,ES模塊默認(rèn)嚴(yán)格模式,沒有node_path, 強(qiáng)制文件拓展名,沒有require.main, require, exports, module.exports, __filename, __dirname等,只有import.meta等。
從導(dǎo)出來看,ES MODULE是編譯時(shí)輸出接口,Commonjs是運(yùn)行時(shí)候加載; ES模塊基于活動(dòng)綁定,傳遞只讀引用, 不可改變整體導(dǎo)入對(duì)象(prevent extension & freeze),Commonjs傳遞的緩存值拷貝。Commonjs的導(dǎo)出遵從但同時(shí)也受限于javascript的值賦值與傳遞特性。
var num=1
function add(){
num++
}
module.exports={
num:num, //導(dǎo)出值是值,而不是引用,內(nèi)部的改動(dòng), 不會(huì)影響導(dǎo)出值
add:add
}
//main.js
var mod=require('./module')
console.log(mod.num)//1
mod.add()
console.log(mod.num)//1
//module.js
export var num=1; //導(dǎo)出的是只讀引用,內(nèi)部的值發(fā)生變化會(huì)會(huì)影響[命名導(dǎo)出]
export function add(){ num++;}
//main.js
import {num,add} from './module'
console.log(num);//1
mod.add();
console.log(num);//2?
AMD(CommonJS 瀏覽器端方案)
依賴前置。模塊的依賴需要提前聲明與加載執(zhí)行。
define(['dependencies1','dependencies2'],function(dep1, dep2){/
? ?// dep1 & dep2 已經(jīng)被正確賦值
? ? dep1.func()?
? ?//..... other codes
? ?dep2.func()?
})
CMD(CommonJS 瀏覽器端方案)
依賴就近。模塊的依賴會(huì)被提前加載,但無需提前聲明與執(zhí)行
define(function({
? ?// dependencies1 & dependencies2?已被加載
? ?let dep1 = require('dependencies1')?//依賴此時(shí)執(zhí)行
? ? dep1.func()?
? ?//..... other codes
? let dep2 = require('dependencies2') //依賴此時(shí)執(zhí)行
? ?dep2.func()?
})
UMD
AMD+Commonjs+全局變量三種風(fēng)格的結(jié)合
(function(global,factory){
? ? typeof?exports === 'object' &&?typeof?module !== 'undefined' ? module.exports = factory():
? ? typeof?define === 'function' && define.amd ? define(factory):
? ? (global.libName = factory());
}(this,(function(){ 'use strict';})));