前兩天有朋友拿了這樣一段代碼來(lái)問(wèn)我严嗜,“我想把一段代碼寫(xiě)成模塊化的樣子晋涣,你幫我看看是不是這樣的笨鸡“邕叮”缤弦,代碼大概是這樣的:
(function(global) {
var myModules = {
name: 'xxx',
location: 'chengdu',
intro: function() {
return `his name is ${myModules.name} and come from ${myModules.location}`
},
}
// some other code...
if(typeof module === 'undefined')
global.myModules = myModules
else
module.exports = myModules
})(this)
“可是我這段代碼在全局還是能
myModules
這個(gè)屬性啊彻磁?”
我一臉懵碍沐,還有這種操作,為什么你的立即執(zhí)行函數(shù)要把this
傳進(jìn)去呢衷蜓,這樣不久將里面的內(nèi)容掛在到了window
上了嗎累提,他似懂非懂,我只好說(shuō)磁浇,你可能需要回頭去看看AMD
和CMD
規(guī)范了刻恭,我大概能夠理解這其中的緣由,畢竟這段時(shí)間前端發(fā)展的速度飛快扯夭,加上webpack
也不需要自己配鳍贾,包括vue
,react
交洗,angular
在內(nèi)的框架類(lèi)庫(kù)都有一鍵生成項(xiàng)目的工具骑科,從而只需要使用下import * from '*'
和export default {}
,而這種便利會(huì)讓新人不再需要去學(xué)習(xí)基本原理就能快速上手构拳,畢竟現(xiàn)在都ES2018
了呀咆爽。
-
對(duì)象形式
最開(kāi)始的時(shí)候,為了減少全局變量置森,一般會(huì)使用一個(gè)對(duì)象的方式來(lái)對(duì)所有的變量方法進(jìn)行一個(gè)包裝:
var obj = {
a: 1,
b: 2,
sum: function(val) {
return obj.a + obj.b + val
},
rest: function(val) {
return val - obj.a - obj.b
}
}
以上代碼似乎解決了全局變量的問(wèn)題斗埂,但是其中的
a
和b
兩個(gè)變量還是可能被修改,其中包含的進(jìn)化有限凫海。
-
立即執(zhí)行函數(shù)
回過(guò)頭來(lái)看文章開(kāi)頭的代碼呛凶,姑且不論以上代碼的錯(cuò)誤之處,稍作修改行贪,這算是最初的一種關(guān)于JavaScript
模塊化的開(kāi)端漾稀,立即執(zhí)行函數(shù):
var add = (function(){
var a = 1
var b = 2
function sum(count){
return a + b + count
}
function rest(count){
return count - a - b
}
return {
rest: rest,
sum: sum
}
})()
這就是一種最簡(jiǎn)單的模塊化的方式,利用閉包的特性建瘫,設(shè)置了兩個(gè)只有在被暴露出來(lái)的
add
和reduce
方法內(nèi)部才能訪問(wèn)到的兩個(gè)變量崭捍,從而保證了函數(shù)的局部變量不可被修改的特性,這次的進(jìn)化用到了閉包啰脚,從而實(shí)現(xiàn)了部分有效的目的殷蛇。
-
放大模式
其實(shí)開(kāi)頭的代碼更符合另外一種叫做放大模式的方法,不過(guò)一般來(lái)說(shuō)不會(huì)講window
作為放大模式中被傳入的對(duì)象
var globalObject = {
fn1: function() {
// todo...
},
// ...
}
globalObject = (function(obj) {
var name = 'xxx'
var location = 'xxx'
function sum(val) { /* todo... */ }
function rest(val) { /* todo... */ }
obj.sum = sum
obj.rest = rest
return obj
})(globalObject)
以這種形式來(lái)寫(xiě),可以讓全局變量盡量的減少粒梦,同時(shí)讓一個(gè)立即執(zhí)行函數(shù)中的代碼盡量做到精簡(jiǎn)收擦。
- 但是這種放大模式也存在著問(wèn)題,比如當(dāng)
globalObject
內(nèi)容足夠多的時(shí)候谍倦,很可能會(huì)造成命名重復(fù)的情況,并且以上所有的方式都不可以減少script
標(biāo)簽的數(shù)量泪勒,所以昼蛀,我們還是會(huì)被模塊的加載順序,命名空間沖突等問(wèn)題所困擾圆存,這時(shí)候叼旋,我們應(yīng)該跨入新時(shí)代了。
-
CMD
規(guī)范
CMD
規(guī)范來(lái)自阿里的框架seajs
沦辙,當(dāng)初確實(shí)有挺多人使用夫植,不過(guò)現(xiàn)階段已經(jīng)不再維護(hù)了,我也不會(huì)油讯,就暫時(shí)不說(shuō)了详民,只列出來(lái)。
-
commonjs
同時(shí)陌兑,從2009年開(kāi)始沈跨,JavaScript
就不再只是一種瀏覽器端的腳本語(yǔ)言了,nodejs
的出現(xiàn)讓使用js
開(kāi)發(fā)服務(wù)端變成了可能兔综,隨著node
出現(xiàn)的東西還有一個(gè)叫做commonjs
的規(guī)范饿凛,在這個(gè)規(guī)范中,每個(gè)文件都是一個(gè)模塊软驰,有著自己的作用域涧窒。
譬如,如下代碼
// 文件a.js
var a = 1
// 文件b.js
console.log(a) // a is not defined.
在這樣的特性下锭亏,a.js
和b.js
都有著自己獨(dú)有的作用域纠吴,要在b
中對(duì)a
進(jìn)行訪問(wèn)忧饭,就需要一種加載機(jī)制导盅,一般來(lái)說(shuō)禽绪,有兩種方法能夠做到:
方法1
// 文件a.js
global.a = 1
// 文件b.js
console.log(a) // 1
這種方法掛載在global
上伸眶,當(dāng)然是不可取的纱皆。
方法2
// 文件a.js
exports.a = 1
// 文件b.js
var moduleA = require('./a')
console.log(moduleA.a)
-
AMD
規(guī)范
requirejs
的出現(xiàn)讓script
標(biāo)簽的減少變成了可能猴贰,在requirejs
的時(shí)代揭绑,我們一般會(huì)使用jQuery
坦报,underscore
這類(lèi)的類(lèi)庫(kù)上煤,如果按照往常的樣子我們會(huì)將代碼寫(xiě)成下面這副模樣:
<script src="/js/lib/jquery.min.js"></script>
<script src="/js/lib/underscore.min.js"></script>
<script src="/js/app/index.js"></script>
<script src="/js/app/app.js"></script>
<!-- and so on... -->
這樣的代碼乍一看似乎沒(méi)什么問(wèn)題休玩,但是當(dāng)一個(gè)項(xiàng)目的代碼量上了一個(gè)量級(jí),一切就變得不是這么回事兒了,你會(huì)被困在加載順序拴疤,加載時(shí)間的問(wèn)題上永部,這也就是requirejs
能夠出現(xiàn)的原因了。
在requirejs
中呐矾,你可以如此改寫(xiě)以上代碼:
// `index.js`
require(['js/lib/jquery.min', 'js/lib/underscore.min', 'js/app/app'], function($, _, app) { /* todo... */ })
// `app.js`
define(['js/lib/jquery.min', 'js/lib/underscore.min'], function($, _) { /* todo... */ })
<script data-main="/index.js" src="/js/require.js"></script>
這里當(dāng)然顯得更加優(yōu)雅了苔埋,在requirejs
的推廣過(guò)程中,AMD
規(guī)范也就應(yīng)運(yùn)而生了蜒犯,那么组橄,requirejs
或者說(shuō)AMD
規(guī)范到底解決了什么樣的問(wèn)題呢,主要有幾點(diǎn):
AMD
是“異步模塊定義”的縮寫(xiě)罚随,也就是說(shuō)玉工,其中內(nèi)容是異步加載的,從而讓頁(yè)面不被js
的加載阻塞淘菩,最大程度上的避免了頁(yè)面假死等情況的產(chǎn)生遵班。AMD
的一個(gè)好處在與依賴前置,所有被使用到的模塊都會(huì)被提前加載好潮改,從而加快運(yùn)行速度狭郑。
那么,commonjs
規(guī)范和AMD
規(guī)范有什么區(qū)別呢
- 運(yùn)行環(huán)境不同汇在,
commonjs
規(guī)范只能運(yùn)行在node
端愿阐,而AMD
規(guī)范則被用到瀏覽器端- 由于運(yùn)行環(huán)境的不同,二者的加載機(jī)制也不同趾疚,
commonjs
中的require
是同步執(zhí)行的缨历,而AMD
中則是異步的。
-
ES2015
模塊化
在ES2015
中糙麦,可以使用export
, export default
, import
import * as
等操作符來(lái)作模塊化的功能辛孵,但是這個(gè)規(guī)范現(xiàn)在尚未被任何瀏覽器加入規(guī)范中,我目前的Chrome
版本為63.0.3239.132
赡磅,也無(wú)法原生支持魄缚,不過(guò)現(xiàn)階段我們幾乎都用上了這個(gè)規(guī)范,這一切都只能歸功于babel
,webpack
和rollup
等新工具的出現(xiàn)焚廊,既然如此冶匹,那就擁抱未來(lái)吧,不過(guò)有一點(diǎn)咆瘟,需要在了解原理的前提下嚼隘,不然,倘若有一天袒餐,真的需要我們來(lái)封裝一個(gè)小小的模塊的時(shí)候飞蛹,沒(méi)有了那些工具谤狡,我們?cè)搹暮蜗率帜亍?/p>