手机
当前位置:查字典教程网 >编程开发 >Javascript教程 >seajs1.3.0源码解析之module依赖有序加载
seajs1.3.0源码解析之module依赖有序加载
摘要:这里是seajsloader的核心部分,有些IE兼容的部分还不是很明白,主要是理解各个模块如何依赖有序加载,以及CMD规范。代码有点长,需要...

这里是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)

【seajs1.3.0源码解析之module依赖有序加载】相关文章:

全面详细的jQuery常见开发技巧手册

JavaScript中的small()方法使用详解

原生javascript实现解析XML文档与字符串

js判断鼠标位置是否在某个div中的方法

简介JavaScript中的setTime()方法的使用

JavaScript正则表达式的分组匹配详解

javascript获取文档坐标和视口坐标

JavaScript中setMonth()方法的使用详解

Jquery注册事件实现方法

简介JavaScript中toTimeString()方法的使用

精品推荐
分类导航