背景:
- Javascript 發(fā)展初期就是為了實(shí)現(xiàn)簡(jiǎn)單的頁(yè)面交互邏輯
- 現(xiàn)在cpu欣福,瀏覽器性能得到極大的提升俄精,很多頁(yè)面邏輯移動(dòng)到了客戶端(表單驗(yàn)證)
- web2.0 時(shí)代的到來(lái)效床,Ajax 技術(shù)得到廣泛應(yīng)用棺聊,jQuery 等前端庫(kù)層出不窮耀销,前端代碼日益膨脹
導(dǎo)致:
- JavaScript 作為嵌入式的腳本語(yǔ)言的定位動(dòng)搖
- JavaScript 沒(méi)有為組織代碼提供明顯幫助
- 甚至沒(méi)有類的概念自脯,更沒(méi)有模塊(module)的概念了
- JavaScript 及其簡(jiǎn)單的代碼組織規(guī)范不足以駕馭如此龐大規(guī)模代碼
模塊
- 這里我們借鑒一下 Java 語(yǔ)言時(shí)怎么處理大規(guī)模程序設(shè)計(jì)的
import java.util.ArrayList;
- 遺憾的是 JavaScript 在設(shè)計(jì)定位原因之景,沒(méi)有提供類似的功能,開發(fā)者需要模擬出類似的功能膏潮,來(lái)隔離锻狗、組織復(fù)雜的 JavaScript 代碼,我們稱為模塊化
- 一個(gè)模塊就是實(shí)現(xiàn)特定功能的文件焕参,有了模塊轻纪,我們就可以更方便的使用別人的代碼,想要什么功能叠纷,就加載什么模塊刻帚。模塊開發(fā)需要遵循一定的規(guī)范
- 規(guī)范形成的過(guò)程是痛苦的,前端的先驅(qū)在刀耕火種涩嚣、茹毛飲血的階段開始崇众,發(fā)展到現(xiàn)在初具規(guī)模掂僵,簡(jiǎn)單了解一下這段不凡的歷程
函數(shù)
- 函數(shù)一個(gè)功能就是實(shí)現(xiàn)特定邏輯的一組語(yǔ)句打包
- JavaScript 的作用域就是基于函數(shù)的
- 所以把函數(shù)作為模塊化的第一步是很自然的事情
- 在一個(gè)文件里面編寫幾個(gè)相關(guān)函數(shù)就是最開始的模塊了
function fn1(){
statement
}
function fn2(){
statement
}
這樣在需要的以后加載函數(shù)所在文件,調(diào)用函數(shù)就可以了
缺點(diǎn):污染了全局變量顷歌,無(wú)法保證不與其他模塊發(fā)生變量名沖突锰蓬,而且模塊成員之間沒(méi)有什么關(guān)系
對(duì)象
為了解決上面問(wèn)題,對(duì)象的寫法因應(yīng)而生眯漩,可以把所有的模塊成員封裝在一個(gè)對(duì)象中
var myModule = {
var a = 1,
var b = 2,
fn1: function(){
},
fn2: function(){
}
}
//調(diào)用
myModule.fn2();
這樣避免了變量污染芹扭,只要保證模塊名唯一即可,同時(shí)同一模塊內(nèi)的成員也有了一定關(guān)系
缺點(diǎn):外部可以隨意修改內(nèi)部成員坤塞,會(huì)產(chǎn)生意外的安全問(wèn)題
myModule.a = 100
立即執(zhí)行函數(shù)
可以通過(guò)立即執(zhí)行函數(shù)冯勉,來(lái)達(dá)到隱藏細(xì)節(jié)的目的
var myModule = (function(){
var a = 1
var b = 2
function fn1(){
}
function fn2(){
}
return {
fn1: fn1,
fn2: fn2
}
})()
這樣在模塊外部就無(wú)法修改我們沒(méi)有暴露出來(lái)的變量名、函數(shù)
上述做法就是我們模塊化的基礎(chǔ)摹芙,目前灼狰,通行的 JavaScript 模塊規(guī)范主要有兩種:CommonJs
和AMD
CommonJs
- 在網(wǎng)頁(yè)端沒(méi)有模塊化編程只是頁(yè)面
JavaScript
邏輯復(fù)雜,但也可以工作下去 - 在服務(wù)器端卻一定要有模塊
- 所以雖然
JavaScript
在web
端發(fā)展這么多年浮禾,第一個(gè)流行的模塊化規(guī)范卻由服務(wù)器端的JavaScript
應(yīng)用帶來(lái) -
CommonJS規(guī)范 是由
NodeJS
發(fā)揚(yáng)光大交胚,這標(biāo)志著JavaScript模塊化編程正式登上舞臺(tái)
簡(jiǎn)單說(shuō)明:
-
定義模塊:根據(jù)
CommonJs
規(guī)范,一個(gè)單獨(dú)的文件就是一個(gè)模塊盈电。每一個(gè)模塊都是一個(gè)單獨(dú)的作用域蝴簇,也就是說(shuō),在該模塊內(nèi)部定義的變量匆帚,無(wú)法被其他模塊讀取熬词,除非定義為global
對(duì)象屬性 -
模塊輸出:模塊只有要給出口,
module.exports
對(duì)象吸重,我們需要把模塊希望輸出的內(nèi)容放入該對(duì)象 -
加載模塊:加載模塊使用
require
方法互拾,該方法讀取一個(gè)文件并執(zhí)行,返回文件內(nèi)部的module.exports
對(duì)象
//模塊定義 myModule.js
var name = 'liu'
function printName(){
console.log(name)
}
function printFullName(firstName){
console.log(firstName + name)
}
module.exports = {
printName: printName,
printFullName: printFullName
}
//加載模塊
var nameModule = require('./myModule.js')
nameModule.printName()
不同的實(shí)現(xiàn)對(duì)require
時(shí)的路徑有不同要求嚎幸,一般情況可以省略js
拓展名颜矿,可以使用相對(duì)路徑,也可以使用絕對(duì)路徑嫉晶,甚至可以省略路徑直接使用模塊名(前提是該模塊時(shí)系統(tǒng)內(nèi)置模塊)
尷尬的瀏覽器
- 上面的代碼
require
是同步的骑疆。模塊系統(tǒng)需要同步讀取模塊文件內(nèi)容,并編譯執(zhí)行以得到模塊接口 - 這在服務(wù)器端實(shí)現(xiàn)和簡(jiǎn)單替废,也很自然箍铭,然而,想在瀏覽器端實(shí)現(xiàn)問(wèn)題卻很多
問(wèn)題:瀏覽器端椎镣,加載JavaScript
最佳诈火、最容易的方式是在document
中插入script
標(biāo)簽。但腳本標(biāo)簽天生異步衣陶,傳統(tǒng)的CommonJS
模塊在瀏覽器環(huán)境中無(wú)法正常使用加載
解決:
- 思路之一是柄瑰,開發(fā)一個(gè)服務(wù)器端組件闸氮,對(duì)模塊代碼作靜態(tài)分析,將模塊與它的依賴列表一起返回給瀏覽器端教沾。這很好使蒲跨,但需要服務(wù)器安裝額外的組件,并因此要調(diào)整一系列底層架構(gòu)
- 另一種解決思路是授翻,用一套標(biāo)準(zhǔn)模板來(lái)分裝模塊定義或悲,但是對(duì)模塊應(yīng)該怎么定義和怎么加載,又產(chǎn)生了分歧
AMD
- AMD (Asynchornous Module Dfinition)堪唐,中文名是異步模塊定義的意思巡语。它是一個(gè)在瀏覽器模塊開發(fā)的規(guī)范
- 由于不是
JavaScript
原生支持,使用 AMD 規(guī)范進(jìn)行頁(yè)面開發(fā)需要用到對(duì)應(yīng)的庫(kù)函數(shù)淮菠,也就是大名鼎鼎的ReuireJS
男公,實(shí)際上 AMD 是RequireJS
在推廣過(guò)程中對(duì)模塊定義的規(guī)范化的產(chǎn)出
requireJS
主要解決兩個(gè)問(wèn)題:
- 多個(gè)
js
文件可能又依賴關(guān)系,被依賴的文件需要早于依賴它的文件加載到瀏覽器 -
js
加載的時(shí)候?yàn)g覽器會(huì)停止頁(yè)面渲染合陵,加載文件越多枢赔,頁(yè)面失去響應(yīng)時(shí)間越長(zhǎng)
看一個(gè)使用requireJS
的例子
//定義模塊 myModule.js
define(['dependency'], function(){
var name = 'liu'
function printName(){
console.log(name)
}
return {
printName: printName
}
})
//加載模塊
require(['module'], function(my){
my.printName()
})
語(yǔ)法
requireJS
定義了一個(gè)函數(shù)define
,它是全局變量拥知,用來(lái)定義模塊
define(id?, dependency?, factory)
- id:可選參數(shù)踏拜,用來(lái)定義模塊的標(biāo)識(shí),如果沒(méi)有提供該參數(shù)低剔,腳本文件名(去掉拓展名)
- dependencies:是一個(gè)當(dāng)前模塊依賴的模塊名稱數(shù)組
- factory:工廠方法速梗,模塊初始化要執(zhí)行的函數(shù)或?qū)ο蟆H绻麨楹瘮?shù)襟齿,它應(yīng)該只被執(zhí)行一次姻锁。如果是對(duì)象,此對(duì)象應(yīng)該為模塊的輸出值
在頁(yè)面上使用require
函數(shù)加載模塊
require([dependencies], funciton(){
})
require()
函數(shù)接收兩個(gè)參數(shù)
- 第一個(gè)參數(shù)是一個(gè)數(shù)組蕊唐,表示所依賴的模塊
- 第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù)屋摔,當(dāng)前頁(yè)面指定的模塊都加載成功后烁设,它將被調(diào)用替梨。加載的模塊會(huì)以參數(shù)形式傳入該函數(shù),從而在回調(diào)函數(shù)內(nèi)部就可以使用這些模塊
require()
函數(shù)在加載依賴的函數(shù)的時(shí)候是異步加載的装黑,這樣瀏覽器不會(huì)失去響應(yīng)副瀑,它執(zhí)行的回調(diào)函數(shù),只有前面的模塊都加載成功后恋谭,才會(huì)運(yùn)行糠睡,解決了依賴性的問(wèn)題
CMD
CMD (Common Module Definition) 通用模塊定義, CMD 規(guī)范是國(guó)內(nèi)發(fā)展出來(lái)的疚颊,就像是 AMD 有個(gè)requireJS
狈孔,CMD 有個(gè)瀏覽器的實(shí)現(xiàn)seaJS
信认,SeaJS
要解決的問(wèn)題和requireJS
一樣,只不過(guò)在模塊定義方式和模塊加載(可以說(shuō)運(yùn)行均抽、解析)時(shí)機(jī)上有所不同
語(yǔ)法
SeaJS
推薦一個(gè)模塊一個(gè)文件嫁赏,遵循統(tǒng)一的寫法
define
define(id?, deps?, factory)
因?yàn)?CMD 推薦
- 一個(gè)文件一個(gè)模塊,所以經(jīng)常就用文件名作為模塊
id
- CMD 推薦依賴就近油挥,所以一般不再
define
的參數(shù)中寫依賴潦蝇,在factory
中寫
factory
有三個(gè)參數(shù)
function(require, exports, module)
require
require
是factory
函數(shù)的第一個(gè)參數(shù)
requrie(id)
require
是一個(gè)方法,接收模塊標(biāo)識(shí)作為唯一參數(shù)深寥,用來(lái)獲取其他模塊提供的接口
exports
exports
是一個(gè)對(duì)象攘乒,用來(lái)向外提供模塊接口
module
module
是一個(gè)對(duì)象,上面存儲(chǔ)了與當(dāng)前模塊相關(guān)聯(lián)的一些屬性和方法
下面是一個(gè)例子
//定義模塊 myModule.js
define(function(require, exports, module){
var $ = require('jquery.js')
$('div').addClass('active')
})
//加載模塊
seajs.use(['myModule.js'], funciton(my){
})
AMD 與 CMD 的區(qū)別
最明顯的區(qū)別就是:在模塊定義時(shí)對(duì)依賴的處理不同
- AMD 推薦依賴前置惋鹅,在定義模塊的時(shí)候就要聲明其依賴的模塊
- CMD 推薦就近依賴则酝,只有在用到某模塊的時(shí)候再去
require
這種區(qū)別各有優(yōu)劣,知識(shí)語(yǔ)法上的差距闰集,而且requireJS
和SeaJS
都支持對(duì)方的寫法
AMD 和 CMD 最大的區(qū)別是:對(duì)依賴模塊的執(zhí)行時(shí)機(jī)處理不同堤魁,注意不是加載的時(shí)機(jī),而是方式不同
- 同樣都是異步加載模塊返十,AMD 在加載模塊完成后就會(huì)執(zhí)行該模塊妥泉,所有模塊都加載執(zhí)行完后會(huì)進(jìn)入
require
的回調(diào)函數(shù),執(zhí)行主邏輯洞坑,這樣的效果就是依賴模塊的執(zhí)行順序和書寫順序不一定一致盲链,看網(wǎng)絡(luò)速度,哪個(gè)先下載下來(lái)迟杂,哪個(gè)先執(zhí)行刽沾,但主邏輯一定在所有依賴加載完成后才執(zhí)行 - CMD 加載完某個(gè)依賴模塊后并不執(zhí)行,知識(shí)下載而已排拷,在所有依賴模塊加載完成后進(jìn)入主邏輯侧漓,遇到
require
語(yǔ)句的時(shí)候才執(zhí)行對(duì)應(yīng)的模塊,這樣模塊的執(zhí)行順序和書寫順序完全一致了
這也是很多人說(shuō) AMD 用戶體驗(yàn)好监氢,因?yàn)闆](méi)有延遲布蔗,依賴模塊提前執(zhí)行了,CMD 性能好浪腐,因?yàn)橹挥杏脩粜枰臅r(shí)候才執(zhí)行的原因