本文談?wù)摰拇a版本是Sea.js 2.3.0替饿,seajs最新的版本是3.0,3.0版本變動(dòng)較大.
新事物的出現(xiàn)或多或少的是想改變已有的行為方式贸典,解決遇到的問(wèn)題视卢。關(guān)于這段歷史,大家可以看看這幾篇文章:why-seajs1廊驼、why-seajs2
關(guān)于seajs原理介紹和分析的文字也有不少据过,例如:Sea.js是如何工作的?妒挎、實(shí)例解析 SeaJS 內(nèi)部執(zhí)行過(guò)程 - 從 use 說(shuō)起绳锅、也有人分析了seajs代碼中的數(shù)據(jù)結(jié)構(gòu):SeaJSV1.1.0-代碼結(jié)構(gòu)和數(shù)據(jù)結(jié)構(gòu)
既然已經(jīng)有很多優(yōu)秀的文章介紹seajs了,為什么你還要浪費(fèi)時(shí)間呢酝掩?
因?yàn)槟X子不好使鳞芙,寫(xiě)作的過(guò)程使我審視自己的結(jié)論,加深了我對(duì)事物的理解期虾。
seajs干嘛的
SeaJS是一個(gè)遵循CMD規(guī)范的JavaScript模塊加載框架原朝,用以實(shí)現(xiàn)JavaScript的模塊化開(kāi)發(fā)及加載機(jī)制
注意關(guān)鍵詞:CommonJS規(guī)范,模塊加載镶苞,模塊化開(kāi)發(fā)
CMD規(guī)范全稱(chēng)Common Module Definition喳坠,該規(guī)范明確了模塊的基本書(shū)寫(xiě)格式和基本交互規(guī)則,即定義了一組模塊化開(kāi)發(fā)的接口宾尚,具體細(xì)節(jié)可以參看:CMD 模塊定義規(guī)范丙笋、英文 Common Module Definition
有C++,Java煌贴,Python開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)應(yīng)該很容易理解模塊化開(kāi)發(fā)和模塊加載的概念御板,以java為例,類(lèi)是java的最小開(kāi)發(fā)單位牛郑,一個(gè)類(lèi)可以看做是一個(gè)最小模塊怠肋,一個(gè)或多個(gè)類(lèi)可以組成一個(gè)package,這種組織代碼的方式就是模塊化開(kāi)發(fā)
當(dāng)我們?cè)谝粋€(gè)類(lèi)中import另一個(gè)package的內(nèi)容時(shí)淹朋,import的內(nèi)容最終會(huì)被包含進(jìn)當(dāng)前類(lèi)中笙各,seajs要做的一個(gè)事情就是模擬java中import這種模塊加載機(jī)制。
與服務(wù)器端不同础芍,在瀏覽器端實(shí)現(xiàn)模塊加載又有其特點(diǎn):模塊(js文件)需要經(jīng)過(guò)網(wǎng)絡(luò)傳輸?shù)竭_(dá)用戶(hù)瀏覽器杈抢,那么何時(shí)將js文件下載到用戶(hù)本地?性能考慮仑性,提前將用到的js文件下載到用戶(hù)本地是首選惶楼,免去了執(zhí)行期從服務(wù)器請(qǐng)求文件消耗的時(shí)間,但同時(shí)seajs也提供了異步加載模塊的功能诊杆。
兩個(gè)核心
1. 模塊的依賴(lài)加載
如何從服務(wù)器獲取js文件歼捐,這里就不詳細(xì)解釋了,原理就是append script標(biāo)簽的方式晨汹。
模塊間的關(guān)系無(wú)非就是依賴(lài)和被依賴(lài)的關(guān)系豹储,具體到兩個(gè)模塊上,有下圖三種關(guān)系:
- a模塊依賴(lài)b模塊
- b模塊依賴(lài)a模塊
- a淘这,b模塊相互依賴(lài)
前兩種情況很好理解剥扣,對(duì)于第三種情況,seajs在3.0版本前是不支持循環(huán)依賴(lài)的慨灭,具體表現(xiàn)為模塊加載會(huì)中斷朦乏,a.doSomething()并沒(méi)有執(zhí)行,3.0的循環(huán)依賴(lài)處理和node一樣了#1382氧骤,下面的代碼可以用來(lái)測(cè)試這個(gè)問(wèn)題呻疹。
seajs.use("a" , function(a){
a.doSomething();
});
//a.js
define(function(require, exports , module) {
var b = require("b");
exports.doSomething = function() {
console.log("in a");
};
});
//b.js
define(function(require, exports, module) {
var c = require("c");
exports.doSomething = function() {
console.log("in b");
};
});
//c.js
define(function(require, exports, module) {
var a = require("a");
exports.doSomething = function() {
console.log("in c");
};
});
提煉一下上面描述的內(nèi)容就是:對(duì)于一個(gè)模塊M,它應(yīng)該擁有下面的關(guān)系
Module{
a: {Module依賴(lài)的模塊}
b: {依賴(lài)Module的模塊}
}
在看看seajs中Module的定義:
function Module(uri, deps) {
this.uri = uri
this.dependencies = deps || []//我依賴(lài)的模塊
this.exports = null
this.status = 0 //我當(dāng)前的狀態(tài)
this._waitings = {} //依賴(lài)我的模塊
this._remain = 0 //我依賴(lài)的模塊還有多少?zèng)]有完成加載
}
以一個(gè)實(shí)際的例子說(shuō)明一下seajs模塊加載的邏輯筹陵,如下圖:
- a依賴(lài)b和c
- b依賴(lài)d
盡管存在上述依賴(lài)刽锤,但是a,b朦佩,c并思,d模塊download到瀏覽器端的順序確是a,b语稠,c宋彼,d弄砍,而不是d,b输涕,c音婶,a,笨想一下后一種執(zhí)行順序也是不可能的莱坎,因?yàn)槟K間的依賴(lài)只有download到瀏覽器端seajs才能進(jìn)行分析衣式。
概括一下整個(gè)加載的流程就是:
自頂向下的download,自底向上的反饋準(zhǔn)備就緒檐什。
如何做到的呢碴卧?
主要是Module中的幾個(gè)屬性發(fā)揮的作用,模塊被download到瀏覽器端后乃正,按照CMD規(guī)范住册,define函數(shù)會(huì)被執(zhí)行,module.define會(huì)分析該模塊的依賴(lài)瓮具,記錄到dependencies屬性中界弧,define函數(shù)執(zhí)行完畢,綁定在script標(biāo)簽上的onload事件會(huì)被觸發(fā)搭综,進(jìn)而加載當(dāng)前模塊的依賴(lài)模塊垢箕,也就是執(zhí)行module.load函數(shù),這是一個(gè)循環(huán)往復(fù)的過(guò)程兑巾。
假設(shè)d模塊加載就緒条获,執(zhí)行module.load時(shí)發(fā)現(xiàn),d模塊已無(wú)其他依賴(lài)蒋歌,進(jìn)而執(zhí)行module.onload, 在module.onload中帅掘,通過(guò)_waitings屬性找到父模塊,操作父模塊的依賴(lài)計(jì)數(shù)_remain堂油,達(dá)到通知父模塊的目的修档。
這是一個(gè)完美的反饋系統(tǒng)。
seajs的模塊加載可以分為兩部分府框,一部分就是模塊加載的過(guò)程吱窝,另一部分就是模塊執(zhí)行的過(guò)程。相比較而言迫靖,執(zhí)行過(guò)程就比較簡(jiǎn)單了院峡,具體可以看源碼module.exec這個(gè)函數(shù)。
下一節(jié)分析seajs中模塊id的解析原理系宜。