您好,欢迎访问一九零五行业门户网

seajs1.3.0源码解析之module依赖有序加载_javascript技巧

这里是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 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
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 --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 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 // 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 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)
其它类似信息

推荐信息