入口 seajs.use
seajs.use直接调用Module.use(),Module.use的源码如下:
// Use function is equal to load a anonymous module
// ids:模块标识,uri是dirname + '_use_0' + 数字自增变量
Module.use = function (ids, callback, uri) {
// 从缓存cachedMods中获取已存在的模块对象或者新建module对象,并将缓存写入其中,返回当前模块对象
var mod = Module.get(uri, isArray(ids) ? ids : [ids])
// 对使用seajs.use或者require.async的模块挂在一个callback。seajs.use的callback在所有依赖都loaded完毕之后才执行,通过require递归的exec()各个已经loaded的依赖模块。这个callback作为seajs的入口,是所有模块的factory开始执行的线头起点。
mod.callback = function() {
var exports = []
// resolve通过正则表达式解析当前模块的依赖deps,并返回解析后的完成路径,其值为一个数组
var uris = mod.resolve()
// seajs.use入口处的依赖模块依次开始执行,此为模块执行的起点
for (var i = 0, len = uris.length; i < len; i++) {
exports[i] = cachedMods[uris[i]].exec()
}
if (callback) {
callback.apply(global, exports)
}
delete mod.callback
}
mod.load()
}
// Get an existed module or create a new one
Module.get = function(uri, deps) {
return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
}
// Module构造函数
function Module(uri, deps) {
this.uri = uri
this.dependencies = deps || []
this.exports = null
this.status = 0
// Who depends on me
this._waitings = {} // 依赖当前模块的对象
// The number of unloaded dependencies
this._remain = 0 // 依赖的未加载完毕的模块个数
}
load:加载当前模块的依赖,并且在当前模块的所有依赖都加载完毕后,触发onload函数
// Load module.dependencies and fire onload when all done
Module.prototype.load = function() {
var mod = this
// If the module is being loaded, just wait it onload call
// 如果当前模块正在被加载或者已经加载完毕,直接返回
if (mod.status >= STATUS.LOADING) {
return
}
// 设置当前模块为加载状态
mod.status = STATUS.LOADING
// Emit `load` event for plugins such as combo plugin
// 解析当前加载模块所有依赖文件的完整路径
var uris = mod.resolve()
emit("load", uris, mod)
// 初始化模块所依赖的未加载完毕的模块的个数
var len = mod._remain = uris.length
var m
// Initialize modules and register waitings
// 依次初始化当前mod依赖的deps为module对象,并且初始化_waitings,并存入cacheMods
for (var i = 0; i < len; i++) {
m = Module.get(uris[i])
if (m.status < STATUS.LOADED) {
// Maybe duplicate: When module has dupliate dependency, it should be it's count, not 1
// 如果当前依赖为未加载完成状态,则该模块的waiting + 1
m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
} else {
// 当前依赖已经加载完成,则模块的remain - 1
mod._remain--
}
}
// 当前模块的所有依赖都已经加载完毕时,触发onload
if (mod._remain === 0) {
mod.onload()
return
}
// Begin parallel loading
var requestCache = {}
// 通过fetch函数,加载当前mod依赖的所有模块
for (i = 0; i < len; i++) {
m = cachedMods[uris[i]]
if (m.status < STATUS.FETCHING) {
m.fetch(requestCache)
} else if (m.status === STATUS.SAVED) {
m.load()
}
}
// Send all requests at last to avoid cache bug in IE6-9. Issues#808
for (var requestUri in requestCache) {
if (requestCache.hasOwnProperty(requestUri)) {
requestCache[requestUri]()
}
}
}
// Call this method when module is loaded
// 模块的所有依赖都加载完毕后,执行onload
Module.prototype.onload = function() {
var mod = this
mod.status = STATUS.LOADED
// 对于使用require.async定义的模块,有callback函数,在所有依赖模块加载完毕后执行callback函数
if (mod.callback) {
mod.callback()
}
// Notify waiting modules to fire onload
// 当前模块的所有依赖都加载完毕后,通知依赖当前模块的所有模块
var waitings = mod._waitings;
var uri, m;
for (uri in waitings) {
if (waitings.hasOwnProperty(uri)) {
m = cachedMods[uri]
m._remain -= waitings[uri]
if (m._remain === 0) {
// 递归调用依赖当前模块的模块,执行onload函数,直到module.use顶端
m.onload()
}
}
}
// Reduce memory taken
delete mod._waitings
delete mod._remain
}
fetch: fetch主要是通过创建<script async=true charset></script>标签并且append到head来实现依赖的加载。这里的依赖都是通过async来异步加载的,加载完毕之后立刻执行define函数,在模块文件执行完毕后(包括define和其他js代码),触发script的onload事件。
// Fetch a module
Module.prototype.fetch = function(requestCache) {
var mod = this
var uri = mod.uri
mod.status = STATUS.FETCHING
// Emit `fetch` event for plugins such as combo plugin
var emitData = {
uri: uri
}
emit("fetch", emitData)
var requestUri = emitData.requestUri || uri
// Empty uri or a non-CMD module
if (!requestUri || fetchedList[requestUri]) {
mod.load()
return
}
// fetchingList和callbackList为两个全局对象,分别存放正在加载的模块列表?????
if (fetchingList[requestUri]) {
callbackList[requestUri].push(mod)
return
}
fetchingList[requestUri] = true
callbackList[requestUri] = [mod]
// Emit `request` event for plugins such as text plugin
emit("request", emitData = {
uri: uri,
requestUri: requestUri,
onRequest: onRequest,
charset: data.charset
})
if (!emitData.requested) {
requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest()
}
function sendRequest() {
seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
}
// script标签的onload事件发生后,触发onRequest方法
function onRequest() {
delete fetchingList[requestUri]
fetchedList[requestUri] = true
// Save meta data of anonymous module
if (anonymousMeta) {
Module.save(uri, anonymousMeta)
anonymousMeta = null
}
// Call callbacks
var m, mods = callbackList[requestUri]
delete callbackList[requestUri]
while ((m = mods.shift()))
m.load()
}
}
request: 将script添加到head,并且定义onload事件
seajs.request = function (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, url)
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 = null
}
function addOnload(node, callback, isCSS, url) {
var supportOnload = "onload" in node
// for Old WebKit and Old Firefox
if (isCSS && (isOldWebKit || !supportOnload)) {
setTimeout(function() {
pollCss(node, callback)
}, 1) // Begin after node insertion
return
}
if (supportOnload) {
node.onload = onload
node.onerror = function() {
emit("error", {
uri: url,
node: node
})
onload()
}
} else {
node.onreadystatechange = function() {
if (/loaded|complete/.test(node.readyState)) {
onload()
}
}
}
function onload() {
// Ensure only run once and handle memory leak in IE
node.onload = node.onerror = node.onreadystatechange = null
// Remove the script to reduce memory leak
if (!isCSS && !data.debug) {
head.removeChild(node)
}
// Dereference the node
node = null
callback()
}
}
Module.define
// Define a module
Module.define = function(id, deps, factory) {
var argsLen = arguments.length
// define(factory)
if (argsLen === 1) {
factory = id
id = undefined
} else if (argsLen === 2) {
factory = deps
// define(deps, factory)
if (isArray(id)) {
deps = id
id = undefined1 `1` // define(id, factory)
``
} else {
deps = undefined
}
}
// Parse dependencies according to the module factory code
if (!isArray(deps) && isFunction(factory)) {
deps = parseDependencies(factory.toString())
}
var meta = {
id: id,
uri: Module.resolve(id),
deps: deps,
factory: factory
}
// Try to derive uri in IE6-9 for anonymous modules
if (!meta.uri && doc.attachEvent) {
var script = getCurrentScript()
if (script) {
meta.uri = script.src
}
// NOTE: If the id-deriving methods above is failed, then falls back
// to use onload event to get the uri
}
// Emit `define` event, used in nocache plugin, seajs node version etc
emit("define", meta)
// Save information for "saving" work in the script onload event
meta.uri ? Module.save(meta.uri, meta) : anonymousMeta = meta
}
resolve
// Resolve module.dependencies
Module.prototype.resolve = function() {
var mod = this
var ids = mod.dependencies
var uris = []
for (var i = 0, len = ids.length; i < len; i++) {
uris[i] = Module.resolve(ids[i], mod.uri)
}
return uris
}
// Resolve id to uri
Module.resolve = function(id, refUri) {
// Emit `resolve` event for plugins such as text plugin
var emitData = { id: id, refUri: refUri }
emit("resolve", emitData)
return emitData.uri || seajs.resolve(emitData.id, refUri)
}
function id2Uri(id, refUri) {
if (!id) return ""
id = parseAlias(id)
id = parsePaths(id)
id = parseVars(id)
id = normalize(id)
var uri = addBase(id, refUri)
uri = parseMap(uri)
return uri
}
seajs.resolve = id2Uri;
参考文献
https://seajs.github.io/seajs...
https://github.com/seajs/exam...
https://github.com/seajs/seajs
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。