首頁  >  文章  >  web前端  >  seajs1.3.0源碼解析之module依賴有序載入_javascript技巧

seajs1.3.0源碼解析之module依賴有序載入_javascript技巧

WBOY
WBOY原創
2016-05-16 17:48:47992瀏覽

這裡是seajs loader的核心部分,有些IE相容的部分還不是很明白,主要是理解各個模組如何依賴有序加載,以及CMD規範。

程式碼有點長,要耐心看:

複製程式碼 程式碼

/**
* loader的核心
*/
;(function(seajs, util, config) {
// 模組快取
var cachedModules = {}
// 介面修改快取
var cachedModifiers = {}
// 編譯佇列
var compileStack = []
// 模組狀態
var STATUS = {
'FETCHING': 1, // The // The module file is fetching now. 模組正在下載
'FETCHED': 2, // The module file has been fetched. 模組已下載
'SAVED': 3, // Themodule info has been saved. 模組資訊已儲存
'READY': 4, // All dependencies and self are ready to compile. 模組的依賴項已下載,等待編譯
'COMPILING': 5, // The module is in compiling now .模組正在編譯中
'COMPILED': 6 // The module is compiled and module.exports is available. 模組已編譯
}


function Module(uri(
this.uri = uri
this.status = status || 0

// this.id is set when saving
// this.dependencies is set when saving
// this.dependencies is set when saving /ving
/ving / this.factory is set when saving
// this.exports is set when compiling
// this.parent is set when compiling
// this.require is set when compiling
>

Module.prototype._use = function(ids, callback) {
//轉換為數組,統一運算
util.isString(ids) && (ids = [ids])
util.isString(ids) && (ids = [ids])
util.isString(ids) && (ids = [ids])
// 使用模組系統內部的路徑解析機制來解析並返回模組路徑
var uris = resolve(ids, this.uri)

this._load(uris, function() {
// Loads preload files introduced in modules before compiling.
// 在編譯之前,再呼叫preload預載模組
// 因為在程式碼執行期間,隨時可以呼叫seajs.config配置預先載入模組
preload(function() {
// 編譯每個模組,並將各個模組的exports作為參數傳遞給回呼函數
var args = util.map(uris, function(uri) {
return uri ?cachedModules[uri]._compile() : null
})

if (callback) {
// null使回呼函數中this指標為window
callback.apply(null, args)
}
})
})
}

// 主模組載入相依模組(稱為子模組),執行回呼函數
Module. prototype._load = function(uris, callback) {
// 過濾uris數組
// 情況一:緩存中不存在該模組,返回其uri
// 情況二:緩存中存在該模組,但是其status var unLoadedUris = util.filter(uris, function(uri) {
return uri && (!cachedModules[uri] ||
|| cachedModules[uri].status })

var length = unLoadedUris.length
// 如果length為0,表示依賴項為0或都已下載完成,那麼執行回調編譯操作
if (length === 0) {
callback()
return
}

var remain = length

for (var i = 0; i // 閉包,為onFetched函數提供上下文環境
(function(uri) {
// 建立模組物件
var module = cachedModules[uri ] ||
(cachedModules[uri] = new Module(uri, STATUS.FETCHING))
//如果模組已下載,那麼執行onFetched,否則執行fetch操作(請求模組)
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)

function onFetched() {
// cachedModules[uri] is changed in un-correspondence case uri]
// 如果模組狀態為SAVED,表示模組的依賴項已經確定,那麼下載依賴模組
if (module.status >= STATUS.SAVED) {
// 從模組資訊取得依賴模組列表,並進行循環依賴的處理
var deps = getPureDependencies(module)
// 如果存在依賴項,繼續下載
if (deps.length) {
Module.prototype._load (deps, function() {
cb(module)
})
}
// 否則直接執行cb
else {
cb(module)
}
else {
cb(module)
}
else {
cb(module)
}
else {
cb(module)
}
else {
cb(module)
}
// Maybe failed to fetch successfully, such as 404 or non-module.
// In these cases, just call cb function directly.
// 如果下載模組不成功,例如404或模組不規範(程式碼出錯),導致此時模組狀態可能為fetching,或fetched
// 此時直接執行回呼函數,在編譯模組時,模組只會傳回null
else {
cb()
}
}

})(unLoadedUris[i])
}

function cb(module) {
// 更改模組狀態為READY,當remain為0時表示模組依賴都已經下完,那麼執行callback (module || {}).status -- remain === 0 && callback() } } Module.prototype._compile = function() { var module = this // 如果該模組已經編譯過,直接回傳module.exports if (module.status === STATUS.COMPILED) { return module.exports }

// Just return null when:
// 1. the module file is 404.
// 2. the module file is not written with valid module format.
// 33 . other error cases.
// 這裡是處理一些異常情況,此時直接返回null
if (module.status return null
}
// 更改模組狀態為COMPILING,表示模組正在編譯
module.status = STATUS.COMPILING

// 模組內部使用,是一種方法,用來獲取其他模組提供(稱之為子模組)的接口,同步操作
function require(id) {
// 根據id解析模組的路徑
var uri = resolve(id, module.uri)
// 從模組快取中取得模組(注意,其實這裡子模組作為主模組的依賴項是已經被下載下來的)
var child = cachedModules[uri]

// Just return null when uri is invalid .
// 如果child為空,只能表示參數填入錯誤導致uri不正確,那麼直接回傳null
if (!child) {
return null
}

// Avoids circular calls.
// 如果子模組的狀態為STATUS.COMPILING,直接回傳child.exports,避免因為循環依賴反覆編譯模組
if (child.status === STATUS.COMPILING) {
return child.exports
}
// 指向初始化時呼叫目前模組的模組。根據此屬性,可以得到模組初始化時的Call Stack.
child.parent = module
// 傳回編譯過的child的module.exports
return child._compile()
}
}
}
// 模組內部使用,用來非同步載入模組,並在載入完成後執行指定回呼。
require.async = function(ids, callback) {
module._use(ids, callback)
}
// 使用模組系統內部的路徑解析機制來解析並傳回模組路徑。函數不會載入模組,只傳回解析後的絕對路徑。
require.resolve = function(id) {
return resolve(id, module.uri)
}
// 透過此屬性,可以查看所有模組系統載入過的模組。
// 在某些情況下,如果需要重新載入某個模組,可以得到該模組的 uri, 然後透過 delete require.cache[uri] 來將其資訊刪除掉。這樣下次使用時,就會重新取得。
require.cache = cachedModules

// require是一種方法,用來取得其他模組提供的介面。
module.require = require
// exports是一個對象,用來向外提供模組介面。
module.exports = {}
var factory = module.factory

// factory 為函數時,表示模組的建構方法。執行該方法,可以得到模組向外提供的介面。
if (util.isFunction(factory)) {
compileStack.push(module)
runInModuleContext(factory, module)
compileStack.pop() } } compileStack.pop() } // factory 為物件、字串等非函數類型時,表示模組的介面就是該物件、字串等值。
// 如:define({ "foo": "bar" });
// 如:define('I am a template. My name is {{name}}.');
else if (factory !== undefined) {
module.exports = factory
}

// 更改模組狀態為COMPILED,表示模組已編譯
module.status = STATUS.COMPI
// 執行模組介面修改,透過seajs.modify()
execModifiers(module)
return module.exports
}


Module.defidine = func , deps, factory) {
var argsLength = arguments.length
// 根據傳入的參數個數,進行參數符合

// define(factory)
// 一個參數的情況:
// id : undefined
// deps : undefined(後面會根據正則取出依賴模組列表)
// factory : function
if (argsLength === 1) {
factory = id
id = undefined
}
// define(id || deps, factory)
// 兩個參數的情況:

else if (argsLength === 2) {
// 預設:define(id, factory)
// id : '...'
// deps : undefined
// factory : function
factory = deps
deps = undefined

// define(deps, factory)
// 如果第一個參數為陣列:define(deps, factory)
// id : undefined
// deps : [...]
// factory : function
if (util.isArray(id)) {
deps = id
id = undefined
}
}

// Parses dependencies.
// 如果deps不是陣列(即deps未指定值),那麼透過正規表示式解析依賴
if (!util.isArray( deps) && util.isFunction(factory)) {
deps = util.parseDependencies(factory.toString())
}

// 元資訊,之後會將訊息傳遞給對應的module物件中
var meta = { id: id, dependencies: deps, factory: factory }
var derivedUri

// Try to derive uri in IE6-9 for anonymous modules.
/ 對於IE6-9,嘗試透過interactive script取得模組的uri
if (document.attachEvent) {
// Try to get the current script.
// 取得目前的script
var script = util.getCurrentScript()
if (script) {
// 將目前script的url進行unpareseMap操作,與模組快取中key保持一致
derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script) )
}

if (!derivedUri) {
util.log('Failed to derive URI from interactive script for:',
factory.toString(), 'warn')

// NOTE: If the id-deriving methods above is failed, then falls back
// to use onload event to get the uri.
}
}
}
// Gets uri directly for specific module.
// 如果給定id,那麼根據id解析路徑
// 顯然如果沒指定id:
// 對於非IE瀏覽器而言,則返回undefined(derivedUri為空)
// 對於IE瀏覽器則傳回CurrentScript的src
// 若指定id:
// 則皆傳回有seajs解析(resolve)過的路徑url
var resolvedUri = id ? resolve(id) : derivedUri
// uri存在的情況,進行模組資訊儲存
if (resolvedUri) {
// For IE:
// If the first ule in a package is not the cachedModules[derivedUri]
// self, it should assign to the correct module when found.
if (resolvedUri === derivedUri) {
🎜>if (refModule && refModule.realUri &&
refModule.status === STATUS.SAVED) {
cachedModules[derivedUri] = null
}
}
var module = save(resolvedUri, meta)

// For IE:
// Assigns the first module in package to cachedModules[derivedUrl]
if (derivedUri) { / cachedModules[derivedUri] may be undefined in combo case.
if ((cachedModules[derivedUri] || {}).status === STATUS.FETCHING) {
cachedModules[duules> .realUri = derivedUri
}
}
else {
// 將第一個模組儲存到firstModuleInPackage
firstModuleInPackage ||firstModulea} 🎜>// uri不存在的情況,在onload回調中進行模組資訊存儲,那裡有個閉包
else {
// Saves information for "memoizing" work in the onload event.
/ / 因為此時的uri不知道,所以將元信息暫時存儲在anonymousModuleMeta中,在onload回調中進行模塊save操作
anonymousModuleMeta = meta
}

}

// 取得正在編譯的模組
Module._getCompilingModule = function() {
return compileStack[compileStack.length - 1]
}

// 從seajs.cache 快速查看並快速查看取得已載入的模組接口,傳回值是module.exports數組
// selector 支援字串和正規表示式
Module._find = function(selector) {
var matches = []

util.forEach(util.keys(cachedModules), function(uri) {
if (util.isString(selector) && uri.indexOf(selector) > -1 ||
util.isRegExp(selselector ) && selector.test(uri)) {
var module = cachedModules[uri]
module.exports && matches.push(module.exports)
}
})


return matches
}

// 修改模組介面
Module._modify = function(id, modifier) {
var uri = resolve(id)
var module = cachedModules[uri]
////如果模組存在,且處於COMPILED狀態,那麼執行修改介面操作
if (module && module.status === STATUS.COMPILED) {
runInModuleContext(modifier, module)
}
///否則放入修改介面快取
else {
cachedModifiers[uri] || (cachedModifiers[uri] = [])
cachedModifiers[uri].push(modifier)
}

return seajs
}


// For plugin developers
Module.STATUS = STATUS
Module._resolve = util.id2Uri Module.cache = cachedModules


// Helpers
// -------
// 正在下載的模組清單
var fetchingList = {}
// 已下載的模組清單
var fetchedList = {}
// 回呼函數清單
var callbackList = {}
// 匿名模組元資訊
var anonymousModuleMeta = nullduleMeta = null
var firstModuleInPackage = null
// 循環依賴堆疊
var circularCheckStack = []

// 批次解析模組的路徑
function resolve(ids, refUri) {> if (util.isString(ids)) {
return Module._resolve(ids, refUri)
}

return util.map(ids, function(id) {
return resolve(return resolve( id, refUri)
})
}

function fetch(uri, callback) {
// fetch時,先將uri按map規則轉換
var requestUri = util. parseMap(uri)
// 在fethedList(已下載的模組列表)中查找,有的話,直接返回,並執行回調函數
// TODO : 為什麼這一步,fetchedList可能會存在該模?
if (fetchedList[requestUri]) {
// See test/issues/debug-using-map
cachedModules[uri] = cachedModules[requestUri]
callback()
>}
// 在fetchingList(正在在下載的模組列表)中查找,有的話,只需添加回調函數到列表中去,然後直接返回
if (fetchingList[requestUri]) {
callbackList[requestUri].push(callback)
return
}
// 如果走到這一步,表示該模組是第一次被請求,
///// 在fetchingList 那麼插入該模組的訊息,表示該模組已經處於下載列表中,並初始化該模組對應的回調函數列表
fetchingList[requestUri] = true
callbackList[requestUri] = [callback]

// Fetches it
// 取得此模組,即發起請求
Module._fetch(
requestUri,

function() {
// 在fetchedList插入該模組的信息,表示該模組插入該模組的信息,表示該模組已經下載完成
fetchedList[requestUri] = true

// Updates module status
var module = cachedModules[uri]
// 此時status可能為STATUS.SAVED,此時在此時在此時_define中已經說過
if (module.status === STATUS.FETCHING) {
module.status = STATUS.FETCHED
}

// Saves anonymous module meta data data 🎜>// 因為是匿名模組(此時透過閉包取得到uri,在這裡儲存模組資訊)
// 並將anonymousModuleMeta置為空
if (anonymousModuleMeta) {
save(uri, anonymousModuleMeta)
anonymousModuleMeta = null
}

// Assigns the first module in package to cachedModules[uri]
// See: test/iues/o. firstModuleInPackage && module.status === STATUS.FETCHED) {
cachedModules[uri] = firstModuleInPackage
firstModuleInPackage.realUri = uri l. 🎜>// 在fetchingList清除模組訊息,因為已經該模組fetched並save
if (fetchingList[requestUri]) {
delete fetchingList[requestUri]
}

/Lists callback
// 依序呼叫回呼函數,並清除回呼函數清單
if (callbackList[requestUri]) {
util.forEach(callbackList[requestUri], function(fn) {
fn()
})
delete callbackList[requestUri]
}

},

config.charset
)
}
uri, meta) {
var module = cachedModules[uri] || (cachedModules[uri] = new Module(uri))

// Don't override already saved module
// 此時status可能有兩個狀態:
// STATUS.FETCHING,在define裡面調用(指定了id),儲存模組資訊
// STATUS.FETCHED,在onload的回呼函數裡調用,儲存模組訊息
if (module.status // Lets anonymous module id equal to its uri
// 匿名模組(即沒有指定id),用它的uri作為id
module.id = meta.id || uri
// 將依賴項(陣列)解析成的絕對路徑,儲存到模組資訊
module.dependencies = resolve(
util.filter(meta. dependencies || [], function(dep) {
return !!dep
}), uri)
// 儲存factory(要執行的模組程式碼,也可能是物件或字串等)
module.factory = meta.factory

// Updates module status
// 更新模組狀態為SAVED,(注意此時它只是擁有了依賴項,還未全部下載下來(即還未READY))
module.status = STATUS.SAVED
}

return module
}

// 依照模組上下文執行模組程式碼」 fn, module) {
// 傳入與模組相關的兩個參數以及模組本身
// exports用來暴露介面
// require用來取得依賴模組(同步)(編譯)
var ret = fn(module.require, module.exports, module)
// 支援回傳值暴露介面形式,如:
// return {
// fn1 : xx
xx/ / ,fn2 : xx
// ...
// }
if (ret !== undefined) {
module.exports = ret
}
}
// 判斷模組是否存在介面修改
function hasModifiers(module) {
return !!cachedModifiers[module.realUri || module.uri]
}
// 修改模組
function execModifiers(module) {
var uri = module.realUri || module.uri
var modifiers = cachedModifiers[uri]
// 內部變數cachedModifiers 就是用來儲存使用者透過透過透過將點
// 看該uri是否又被modify更改過
if (modifiers) {
// 對修改點統一執行factory,回傳修改後的module.exports
util.forEach(modifiers , function(modifier) {
runInModuleContext(modifier, module)
})
// 刪除modify 方法定義的修改點,避免再次執行
delete cachedModifiers[uri]
}
}
}

//取得純粹的依賴關係,得到不存在循環依賴關係的依賴數組
function getPureDependencies(module) {
var uri = module.uri
// 對每個依賴項進行過濾,對於有可能形成循環依賴的進行剔除,並打印出警告日誌
return util.filter(module.dependencies, function(dep) {
// 首先將被檢查模組的uri放到循環依賴檢查堆疊中,之後的檢查會用到
circularCheckStack = [uri]
//接下來檢查模組uri是否和其依賴的模組存在循環依賴
var isCircular = isCircularWaiting(cachedModules[dep ])
if (isCircular) {
// 如果循環,則將uri放到循環依賴檢查堆疊中
circularCheckStack.push(uri)
// 列印出循環警告日誌
printCircularLog(circularCheckStack)
}

return !isCircular
})
}

function isCircularWaiting(module) { 返回false,因為此時也無法獲得依賴模組的依賴項,所以這裡無法做判斷
// 或如果模組的狀態值等於saved,也返回false,因為模組狀態為saved的時候代表該模組的信息已經有了,
// 所以儘管形成了循環依賴,但是require主模組時,同樣可以正常編譯,返回主模組介面(好像nodejs會回傳undefined)
if (!module || module.status !== STATUS.SAVED) {
return false
}
// 如果不是以上的情況,那麼將依賴模組的uri放到循環依賴檢查棧中,之後的檢查會用到
circularCheckStack.push(module.uri)
// 再次取依賴模組的依賴模組
var deps = module.dependencies

if (deps.length) {
// 透過循環依賴檢查棧,檢查是否有循環依賴(這裡是第一層依賴模組檢查,與主模組循環依賴的情況)
if (isOverlap(deps, circularCheckStack)) {
return true
}
// 如果不存在上述情形,那麼進一步查看,依賴模組的依賴模組,查看他們是否存在對循環依賴檢查棧中的uri的模組存在循環依賴
// 這樣的話,就遞歸了,循環依賴檢查堆疊就像形成的一條鏈,當前模組依序對主模組,主模組的主模組...直到最頂上的主模組,依序判斷是否存在依賴
for (var i = 0; i if (isCircularWaiting(cachedModules[deps[i]])) {
return true
}
}
}
////不存在循環依賴,那麼pop出之前已經push進的模組uri,並回傳false
circularCheckStack.pop()
return false
}
// 印出循環警告日誌
function printCircularLog( stack, type) {
util.log('Found circular dependencies:', stack.join(' --> '), type)
}
//判斷兩個陣列是否有重複的值
function isOverlap(arrA, arrB) {
var arrC = arrA.concat(arrB)
return arrC.length > util.unique(arrC).length
}
從配置檔案讀取是否有需要提前載入的模組
// 如果有預先載入模組,首先設定預載模組為空(保證下次不必重複載入),並載入預載模組並執行回調,如果沒有則順序執行
function preload(callback) {
var preloadMods = config.preload.slice()
config.preload = []
preloadMods.length ? globalModule._use(preloadMods, callback) : callback( )
}


// Public API
// 對外暴露的API
// ----------
// 全域模組,可以認為是頁面模組,頁面中的js,css檔案都是透過它來載入的
// 模組初始狀態就是COMPILED,uri就是頁面的uri
var globalModule = new Module(util.pageUri, STATUS.COMPILED)

// 頁面js,css檔案載入器
seajs.use = function(ids, callback) {
// Loads preload modules before all other modules. / 預先載入模組
preload(function() {
globalModule._use(ids, callback)
})

// Chain
return seajs
}


// For normal users
// 供普通使用者呼叫
seajs.define = Module._define
seajs.cache = Module.cache
seajs.find = Module._find
seajs.modify = Module._modify


// For plugin developers
// 供開發者使用
seajs.pluginSDK = {
Module: Mod >util: util,
config: config
}

})(seajs, seajs._util, seajs._config)


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn