在了解模塊化的概念前,首先先解決一個問題 - 為什么需要模塊化杈曲?
先從實際問題出發(fā)例朱,在類似require.js孝情、sea.js、browserify洒嗤、webpack等工具出現(xiàn)之前箫荡,我們可能會遇到如下一些問題:
我們在引用不同的庫或者js文件時,可能會出現(xiàn)命名沖突渔隶。
引用的庫或者文件都會有個先來后到羔挡,那如何去決定與維護這些順序呢?
庫與庫之間间唉、文件與文件之間可能存在循環(huán)引用绞灼,即在a.js中引用了b.js,而b.js中也引用了a.js呈野;兩者相互依賴低矮。那我們?nèi)绾螞Q定先引用哪個件呢?
我們再從一個生活中的例子出發(fā)被冒,簡要了解一下模塊化的優(yōu)點军掂,即為什么需要模塊化的原因。
假設(shè)你有一套修理工具箱昨悼,里面包含了屬于這套修理工具箱的各種型號的螺絲批蝗锥、鉗子和錘子等等。每次家里水管破了率触,燈泡壞了什么的终议,你都可以拿這套修理工具箱進行修理。每次修理可能都會造成一些工具的損耗或損壞葱蝗,損壞之后我們就應(yīng)該去買相同型號的工具進行補充穴张;其他不對頭的工具不會回收回這套修理工具箱中,同樣這套修理工具箱中的工具也不會隨意扔出工具箱中两曼。
我們把上述例子轉(zhuǎn)化為模塊化來看看皂甘。首先,修理工具箱就是模塊合愈,里面的工具就是模塊中的各種變量或函數(shù)叮贩。工具出現(xiàn)損壞等于模塊內(nèi)出了什么問題击狮,這時候我們只需要修復(fù)模塊內(nèi)的bug就好了佛析。其他不對頭的工具不會回收回工具箱中,反之工具箱中的工具不會隨意被扔出表示模塊內(nèi)的變量彪蓬、函數(shù)等不會污染外部的變量寸莫、函數(shù)等等,反之亦然档冬。這套工具箱可以重復(fù)利用也就是模塊的復(fù)用性很強膘茎。
總結(jié)模塊化有三大優(yōu)點:
可維護性強桃纯,更新或修復(fù)模塊內(nèi)的邏輯不會影響外部邏輯
獨立的命名空間,模塊內(nèi)的變量不會污染外部的變量披坏,即使它們擁有相同的變量名
可復(fù)用性強态坦,我們需要在不同的地方用到某個模塊,只需要在對應(yīng)的地方引入它就行了棒拂,無需重復(fù)地拷貝復(fù)制伞梯。
什么是模塊化
var module = (function () {
? var _privateCnt = 0
? var _privateProperty = 'I am private property'
? function _privateCnter () {
? ? return _privateCnt += 1
? }
? function publicCnter () {
? ? return _privateCnter()
? }
? return {
? ? property: _privateProperty,
? ? publicCnter: publicCnter
? }
})()
console.log(module.property) // I am private property
console.log(module.publicCnter()) // 1
console.log(module.publicCnter()) // 2
console.log(module.publicCnter()) // 3
這個例子展示了通過立即執(zhí)行函數(shù)表達式將一些變量、方法暴露出去帚屉,并防止外部直接修改一些我們不希望修改的變量谜诫、方法。這樣做還有一個好處攻旦,就是我們可以快速地了解到這個立即執(zhí)行函數(shù)為我們提供了哪些公共屬性及方法喻旷,而不需要閱讀所有邏輯代碼。這種方式在設(shè)計模式中也稱作模塊模式(Module Pattern)牢屋。
CommonJS
CommonJS主要是為服務(wù)端定義的模塊規(guī)范且预,它一開始的名字為ServerJS。npm生態(tài)系統(tǒng)基本都是基于CommonJS規(guī)范所建立起來的伟阔。
// 在 foo.js 中辣之,我們導(dǎo)出了變量 foomodule.exports = {foo:'foo'}// 在 bar.js,我們通過 require 引入了變量 foovarmodule=require('foo')console.log(module.foo)// foo復(fù)制代碼
看起來很簡單是吧皱炉』彻溃可能有人會問了,這個module是什么東西呢合搅?其實module是 Node 中的一個內(nèi)置對象多搀。我們可以在node環(huán)境下打印看看
// foo,js
module.exports = {
? foo: 'foo'
}
console.log('module: ', module)
// bar.js
var module = require('./foo')
console.log(module.foo)
ES Modules
CommonJS在服務(wù)端中應(yīng)用廣泛隙姿,但由于它是同步加載模塊的梅垄,它在客戶端不太合適;而AMD支持瀏覽器異步加載模塊输玷,但在服務(wù)端卻顯得沒有必要队丝,因此ES Modules出現(xiàn)了。我們先來看看ES Modules是如何工作的欲鹏。
ES Modules與CommonJS很相似机久,較新的瀏覽器均已支持ES Modules,Node.js正在慢慢支持相關(guān)規(guī)范赔嚎。
ES Modules的核心為export與import膘盖,分別對應(yīng)導(dǎo)出模塊與導(dǎo)入模塊。
導(dǎo)出模塊:
// CommonJS
module.exports = foo () {
? console.log('here is foo')
}
// ES Modules
export default function bar () {
? console.log('here is bar')
}
// CommonJS
var foo = require('./foo')
foo() // here is foo
// ES Modules
import bar from './bar'
bar() // here is bar
這兩者這么相似尤误,它們在實際的表現(xiàn)上有什么不同呢侠畔?
我分別在Firefox、Edge和Chrome上測試(Chrome由于自身的安全策略無法直接通過本地文件進行測試损晤,所以利用插件Web Server for Chrome起了個本地服務(wù)器软棺。
測試代碼如下圖(注意我們在使用ES Modules時要給script標簽加上type="module")
? 我們來看看打印結(jié)果
? ? ?開始執(zhí)行 bar.js
? ? ? ? ? ? ? ? ? 開始執(zhí)行 foo.js
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?here is foo
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?here is bar
很顯然CommonJS是同步加載模塊的,所以代碼的執(zhí)行也是順序的尤勋。而ES Modules是異步加載模塊的喘落,且ES Modules是編譯時加載模塊,在運行時(執(zhí)行代碼時)再根據(jù)相關(guān)的引用去被加載的模塊中取值最冰。再詳細一點來說的話瘦棋,整個過程分如下三個步驟:
構(gòu)建 - 查找、下載并將文件解析到模塊記錄中
實例化 - 在內(nèi)存中找到所有導(dǎo)出(export)的值的位置锌奴,但暫不對這些值進行賦值兽狭;然后在內(nèi)存中創(chuàng)建exports和imports的空間憾股。這一步稱為鏈接
運行 - 運行代碼并將實際的值賦予給實例化中導(dǎo)出的值
因此在編譯期間鹿蜀,編譯器先找到了foo.js的依賴bar.js箕慧,先編譯bar.js然后才是foo.js。所以你才會先看到開始執(zhí)行 bar.js茴恰。
總結(jié)
模塊化簡單來說就是將相關(guān)的邏輯代碼獨立出來颠焦,獨立的形式有很多種,可以是單純的一個函數(shù)往枣,亦可以是單獨的一個文件伐庭。
模塊化可以更好地組織代碼結(jié)構(gòu),增強其可維護性分冈,可復(fù)用性強圾另。
CommonJS工作原理為同步加載模塊,在Node.js中有著廣泛的使用雕沉,對客戶端不友好集乔。
AMD工作原理為異步加載模塊。
ES Modules為ES6推出的規(guī)范坡椒,客戶端的支持比較好扰路,Node.js將會慢慢全面支持。它與AMD一樣倔叼,也是異步加載模塊汗唱。