SeaJS 2.0.0源码浅析 - 还是从use说起
æ¬ææ 颿¯æ ¹æ® @pigcan çä¸ç¯æ§æå®ä¾è§£æSeaJSçæºç èåçï¼å¦ä»SeaJS乿´æ°å°äº2.0.0ï¼æäºä»£ç å·²ç»åäºéæä¼åï¼æä»¥æ³éæ°æ¢³çä¸ä»£ç ï¼ä»¥æ¤æåè®°å½ã 便§æ¯çæçindex.js/a.js/b.jsï¼ä»£ç åå«å¦ä¸ï¼ index.js
seajs.use('./a', function(a) {
document.getElementById('console').innerHTML = a;
})
a.js
;define(function(require, exports, module) {
var numInA = 1;
var b = require('./b');
return b.numInB + numInA;
});
b.js
;define(function(require, exports, module) {
return {
numInB: 2
}
});
åå¤å·¥ä½readyï¼ä»£ç è·èµ·æ¥ã 代ç è¿è¡æµç¨å¦ä¸ï¼
- é¦å è¿å ¥å ¥å£å½æ°seajs.use
seajs.use = function(ids, callback) {
// Load preload modules before all other modules
// 2) preload彿°
preload(function() {
use(resolve(ids), callback)
})
return seajs
}
- preload彿°å®ä¹ï¼æå¨ä¼å å è½½å¨seajs.configä¸preloadé ç½®çæ¨¡åï¼ç¶ååå è½½æå®æ¨¡å
function preload(callback) {
var preloadMods = configData.preload
var len = preloadMods.length
if (len) {
// 3) use彿°
use(resolve(preloadMods), function() {
// Remove the loaded preload modules
preloadMods.splice(0, len)
// Allow preload modules to add new preload modules
preload(callback)
})
}
else {
callback()
}
}
注ï¼resolve彿°å®é è°ç¨äºid2url彿°ï¼å¨æ¤å¤ï¼è¯»è åªéè¦è®¤ä¸ºå¯ä»¥å°id转å为urlå³å¯ï¼ å¦ ./a -> http://localhost/test_seajs/a.js 3) use彿°å®ä¹ï¼æ§è¡åè°callbackæ¶ï¼ä¼å°è·åå°çå个模åçexportså¯¹è±¡ä¼ å ¥
function use(uris, callback) {
isArray(uris) || (uris = [uris])
// 4) load彿°
load(uris, function() {
var exports = []
for (var i = 0; i < uris.length; i++) {
exports[i] = getExports(cachedModules[uris[i]])
}
if (callback) {
callback.apply(global, exports)
}
})
}
- load彿°ï¼seajsä¸å®é å è½½js/cssæä»¶ç彿°
function load(uris, callback) {
// è¿æ»¤å·²ç»å è½½è¿ç模åï¼å¹¶ä¸ä¸ºæªå è½½çæ¨¡åå建Moduleå®ä¾ï¼æ¾å
¥å°cachedModulesä¸ä¿å
var unloadedUris = getUnloadedUris(uris)
if (unloadedUris.length === 0) {
callback()
return
}
// Emit `load` event for plugins such as plugin-combo
emit("load", unloadedUris)
var len = unloadedUris.length
var remain = len
for (var i = 0; i < len; i++) {
(function(uri) {
var mod = cachedModules[uri]
if (mod.dependencies.length) {
loadWaitings(function(circular) {
mod.status < STATUS_SAVED ? fetch(uri, cb) : cb()
function cb() {
done(circular)
}
})
}
else {
// 5) fetch彿°
// æ¾ç¶ç¬¬ä¸æ¬¡èµ°è¿ä¸ªåæ¯ï¼æ§è¡fetch(uri, loadWaitings)彿°
mod.status < STATUS_SAVED ?
fetch(uri, loadWaitings) : done()
}
// è¯¥å½æ°ä¼å¨ç»è¿å¦ä¸å½æ°å fetch -> request -> addOnload -> onRequested,
// æåå¨onRequestedä¸è¢«è°ç¨
function loadWaitings(cb) {
cb || (cb = done)
var waitings = getUnloadedUris(mod.dependencies)
if (waitings.length === 0) {
cb()
}
// Break circular waiting callbacks
else if (isCircularWaiting(mod)) {
printCircularLog(circularStack)
circularStack.length = 0
cb(true)
}
// Load all unloaded dependencies
else {
waitingsList[uri] = waitings
load(waitings, cb)
}
}
function done(circular) {
if (!circular && mod.status < STATUS_LOADED) {
mod.status = STATUS_LOADED
}
if (--remain === 0) {
callback()
}
}
})(unloadedUris[i])
}
}
- fetch彿°ï¼åè°è°ç¨loadWaitings彿°
function fetch(uri, callback) {
cachedModules[uri].status = STATUS_FETCHING
// Emit `fetch` event for plugins such as plugin-combo
var data = { uri: uri }
emit("fetch", data)
var requestUri = data.requestUri || uri
if (fetchedList[requestUri]) {
callback()
return
}
if (fetchingList[requestUri]) {
callbackList[requestUri].push(callback)
return
}
fetchingList[requestUri] = true
callbackList[requestUri] = [callback]
// Emit `request` event for plugins such as plugin-text
var charset = configData.charset
emit("request", data = {
uri: uri,
requestUri: requestUri,
callback: onRequested,
charset: charset
})
if (!data.requested) {
// 6) request彿°
// ç¬¬ä¸æ¬¡å è½½./aèµ°æ¤åæ¯
request(data.requestUri, onRequested, charset)
}
// 8) onRequestedåè°
function onRequested() {
delete fetchingList[requestUri]
fetchedList[requestUri] = true
// Save meta data of anonymous module
if (anonymousModuleData) {
save(uri, anonymousModuleData)
anonymousModuleData = undefined
}
// Call callbacks
var fn, fns = callbackList[requestUri]
delete callbackList[requestUri]
while ((fn = fns.shift())) fn()
}
}
- request彿°ï¼çæ£å¾å½å页é¢ä¸æå ¥script/linkæ ç¾ç彿°ï¼å¹¶ä¸å ä¸äºscript/linkçonloadçå¬åè°ã æ¤å¤æè§å¾è®¾è®¡æå¦ï¼å¨æå ¥scriptåï¼æµè§å¨é©¬ä¸ä¸è½½a.jsï¼å¹¶ä¸æ§è¡define代ç åï¼å ä¸ºæ¤æ¶æ²¡ææå®æ¨¡åçidï¼ äºæ¯ä¼çæä¸ä¸ªanonymousModuleDataåéï¼è®°å½äºa.jsçä¾èµb.jsï¼å·¥åæ¹æ³çï¼ èä¸é¢æ§è¡å®åï¼scriptçonloadåè°è¢«è§¦åï¼onloadå é¨è§¦åonRequested彿°ï¼onRequested彿°æ¯å®ä¹å¨fetchå é¨çéå ï¼å好è½åå°æ¤æ¶çuriåæ°åanonymousModuleDataåéï¼ä¸¤è ç»åï¼å好æé åºæè¿°a.js宿´çModuleå®ä¾ã
function request(url, callback, charset) {
var isCSS = IS_CSS_RE.test(url)
var node = doc.createElement(isCSS ? "link" : "script")
if (charset) {
var cs = isFunction(charset) ? charset(url) : charset
if (cs) {
node.charset = cs
}
}
addOnload(node, callback, isCSS)
if (isCSS) {
node.rel = "stylesheet"
node.href = url
}
else {
node.async = true
node.src = url
}
// For some cache cases in IE 6-8, the script executes IMMEDIATELY after
// the end of the insert execution, so use `currentlyAddingScript` to
// hold current node, for deriving url in `define` call
currentlyAddingScript = node
// ref: #185 & http://dev.jquery.com/ticket/2709
baseElement ?
head.insertBefore(node, baseElement) :
head.appendChild(node)
currentlyAddingScript = undefined
}
- æ§è¡define代ç å ä¸é¢å·²ç»è¯´æï¼å¨æ§è¡request彿°æ¶ï¼a.js被æµè§å¨ä¸è½½å¹¶æ§è¡ï¼çæanonymousModuleDataåéã
- æ§è¡onRequestedåè°ï¼onRequestedå
é¨åè°ç¨äºloadWaitings彿°ï¼âæ°å¥½âloadWaitingsæ¯å¨
load彿°ä¸å®ä¹çä¸ä¸ªéå
ï¼è½å¤è®¿é®å°æ¤æ¶load彿°ä¸å è½½çæ¨¡åaModï¼ç»§èåç°aModçä¾èµbModï¼
äºæ¯æ§è¡
load('./b', callback)ï¼å¾ªç¯ç¬¬åæ¥å°ç¬¬å «æ¥
è³æ¤ï¼ä»£ç å·²åºæ¬èµ°å®ï¼åä¸äºåé¡¾ï¼ æ»ä½æè·¯ï¼ä»£ç ä»seajs.useå¼å§ï¼åè°ä¸å±å¥ä¸å±ï¼ç´å°æåç±scriptçonloadè°èµ·onRequested彿°è§¦ååè°ï¼ éå±ä¾æ¬¡æ§è¡ï¼ç´å°åå°æåçåè° --- seajs.useçåè°ï¼æ¤åè°å°æææ¨¡åçexportså¯¹è±¡ä¼ å ¥æ§è¡ã 亮ç¹ï¼
- å¨load䏿¾ç½®éå loadWaitingsæ¥è·åæ¤æ¶loadçæ¨¡å对象ï¼
- å¨fetch䏿¾ç½®éå
onRequestedæ¥è·åæ¤æ¶fetchçuriï¼åæ¤ä¸anonymousModuleDataç»åçæå®æ´çModuleå®ä¾ï¼
为
getExportsæ¹æ³æä¾äºå模åè³å ³éè¦çfactoryæ¹æ³ã
@lifesinger 整个SeaJS代码看下来,感觉代码中回调层层嵌套,结合了script onload的回调,并且闭包运用的也恰到好处,大师就是大师,不佩服都不行。:+1: :+1: :+1: 我看代码时一开始也是用脑袋记各个回调,发现到后来完全混乱了,只好在本子上涂涂画画来记忆,整个设计真的很妙,不知玉伯你设计的时候是怎么想的?有辅助工具?
@LeoYuan 也不是一开始就写成这样,不断重构的结果,呵呵。辅助工具是纸和笔,不断在大脑中重构,慢慢调整完善到现在的代码。
@lifesinger 呵呵,精益求精,不断打磨,不断做有意义的重构,这应该咱们每个程序员都应该效仿的。 还有一个问题需要求证下,玉伯,我上边对于seajs整个流程的描述不知道有没有理解偏差/错误的地方?
没问题的。
2013/4/22 LeoYuan 袁力皓 [email protected]
@lifesinger https://github.com/lifesinger 呵呵,精益求精,不断打磨,不断做有意义的重构,这应该咱们每个程序员都应该效仿的。 还有一个问题需要求证下,玉伯,我上边对于seajs整个流程的描述不知道有没有理解偏差/错误的地方?
— Reply to this email directly or view it on GitHubhttps://github.com/LeoYuan/leoyuan.github.com/issues/7#issuecomment-16756726 .
王保平 / 玉伯(射雕) 送人玫瑰手有余香
少读源码多使用。
@afc163 呵呵,多谢提醒。 不过,其实我也不是源码控,在有时间并且有兴趣的条件下,看一看也未尝不可啊。
乍看一眼你的博客地址,以为是片友,震惊了一下。。。