這裡是seajs loader的核心部分,有些IE兼容的部分還不是很明白,主要是理解各個模塊如何依賴有序加載,以及CMD規范。
代碼有點長,需要耐心看:
復制代碼 代碼如下:
/**
* The core of loader
*/
;(function(seajs, util, config) {
// 模塊緩存
var cachedModules = {}
// 接口修改緩存
var cachedModifiers = {}
// 編譯隊列
var compileStack = []
// 模塊狀態
var STATUS = {
'FETCHING': 1, // The module file is fetching now. 模塊正在下載中
'FETCHED': 2, // The module file has been fetched. 模塊已下載
'SAVED': 3, // The module 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, status) {
this.uri = uri
this.status = status || 0
// this.id is set when saving
// this.dependencies is set when saving
// 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])
// 使用模塊系統內部的路徑解析機制來解析並返回模塊路徑
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 < STATUS.READY(即還沒准備好編譯)
var unLoadedUris = util.filter(uris, function(uri) {
return uri && (!cachedModules[uri] ||
cachedModules[uri].status < STATUS.READY)
})
var length = unLoadedUris.length
// 如果length為0,表示依賴項為0或者都已下載完成,那麼執行回調編譯操作
if (length === 0) {
callback()
return
}
var remain = length
for (var i = 0; i < length; 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
module = cachedModules[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)
}
}
// 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 < STATUS.READY && (module.status = STATUS.READY)
--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.
// 3. other error cases.
// 這裡是處理一些異常情況,此時直接返回null
if (module.status < STATUS.SAVED && !hasModifiers(module)) {
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()
}
// factory 為對象、字符串等非函數類型時,表示模塊的接口就是該對象、字符串等值。
// 如:define({ "foo": "bar" });
// 如:define('I am a template. My name is {{name}}.');
else if (factory !== undefined) {
module.exports = factory
}
// 更改模塊狀態為COMPILED,表示模塊已編譯
module.status = STATUS.COMPILED
// 執行模塊接口修改,通過seajs.modify()
execModifiers(module)
return module.exports
}
Module._define = function(id, 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 module in a package is not the cachedModules[derivedUri]
// self, it should assign to the correct module when found.
if (resolvedUri === derivedUri) {
var refModule = cachedModules[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[derivedUri] = module
module.realUri = derivedUri
}
}
else {
// 將第一個模塊存儲到firstModuleInPackage
firstModuleInPackage || (firstModuleInPackage = module)
}
}
// 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(selector) && 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._fetch = util.fetch
Module.cache = cachedModules
// Helpers
// -------
// 正在下載的模塊列表
var fetchingList = {}
// 已下載的模塊列表
var fetchedList = {}
// 回調函數列表
var callbackList = {}
// 匿名模塊元信息
var anonymousModuleMeta = 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(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()
return
}
// 在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
// 因為是匿名模塊(此時通過閉包獲取到uri,在這裡存儲模塊信息)
// 並將anonymousModuleMeta置為空
if (anonymousModuleMeta) {
save(uri, anonymousModuleMeta)
anonymousModuleMeta = null
}
// Assigns the first module in package to cachedModules[uri]
// See: test/issues/un-correspondence
if (firstModuleInPackage && module.status === STATUS.FETCHED) {
cachedModules[uri] = firstModuleInPackage
firstModuleInPackage.realUri = uri
}
firstModuleInPackage = null
// Clears
// 在fetchingList清除模塊信息,因為已經該模塊fetched並save
if (fetchingList[requestUri]) {
delete fetchingList[requestUri]
}
// Calls callbackList
// 依次調用回調函數,並清除回調函數列表
if (callbackList[requestUri]) {
util.forEach(callbackList[requestUri], function(fn) {
fn()
})
delete callbackList[requestUri]
}
},
config.charset
)
}
function save(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 < STATUS.SAVED) {
// 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
}
// 根據模塊上下文執行模塊代碼
function runInModuleContext(fn, module) {
// 傳入與模塊相關的兩個參數以及模塊自身
// exports用來暴露接口
// require用來獲取依賴模塊(同步)(編譯)
var ret = fn(module.require, module.exports, module)
// 支持返回值暴露接口形式,如:
// return {
// fn1 : 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 就是用來存儲用戶通過 seajs.modify 方法定義的修改點
// 查看該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 < deps.length; 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: Module,
util: util,
config: config
}
})(seajs, seajs._util, seajs._config)