var cachedMods = seajs.cache = {} // 模块的缓存对象 Module.get = function(uri, deps) { return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps)) } function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] this.deps = {} // Ref the dependence modules this.status = 0 this._entry = [] }
绑定的回调函数会在所有模块加载完毕之后调用,我们先跳过,直接看load方法。load方法会先把所有依赖的模块id转为uri,然后进行实例化,最后调用fetch方法,绑定模块加载成功或失败的回调,最后进行模块加载。具体代码如下(代码经过精简):
// 所有依赖加载完毕后执行 onload Module.prototype.load = function() { var mod = this mod.status = STATUS.LOADING // 状态置为模块加载中 // 调用resolve方法,将模块id转为uri。 // 比如之前的"mian",会在前面加上我们之前设置的base,然后在后面拼上js后缀 // 最后变成: "http://qq.com/web/js/main.js" var uris = mod.resolve() // 遍历所有依赖项的uri,然后进行依赖模块的实例化 for (var i = 0, len = uris.length; i < len; i++) { mod.deps[mod.dependencies[i]] = Module.get(uris[i]) } // 将entry传入到所有的依赖模块,这个entry是我们在use方法的时候设置的 mod.pass() if (mod._entry.length) { mod.onload() return } // 开始进行并行加载 var requestCache = {} var m for (i = 0; i < len; i++) { m = cachedMods[uris[i]] // 获取之前实例化的模块对象 m.fetch(requestCache) // 进行fetch } // 发送请求进行模块的加载 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { requestCache[requestUri]() //调用 seajs.request } } }
将模块id转为uri
resolve方法实现可以稍微看下,基本上是把config里面的参数拿出来,进行拼接uri的处理。
Module.prototype.resolve = function() { var mod = this var ids = mod.dependencies // 取出所有依赖模块的id var uris = [] // 进行遍历操作 for (var i = 0, len = ids.length; i < len; i++) { uris[i] = Module.resolve(ids[i], mod.uri) //将模块id转为uri } return uris } Module.resolve = function(id, refUri) { var emitData = { id: id, refUri: refUri } return seajs.resolve(emitData.id, refUri) // 调用 id2Uri } seajs.resolve = id2Uri function id2Uri(id, refUri) { // 将id转为uri,转换配置中的一些变量 if (!id) return "" id = parseAlias(id) id = parsePaths(id) id = parseAlias(id) id = parseVars(id) id = parseAlias(id) id = normalize(id) id = parseAlias(id) var uri = addBase(id, refUri) uri = parseAlias(uri) uri = parseMap(uri) return uri }
最后就是调用了id2Uri,将id转为uri,其中调用了很多的parse方法,这些方法不一一去看,原理大致一样,主要看下parseAlias。如果这个id有定义过alias,将alias取出,比如id为"jquery",之前在定义alias中又有定义jquery: 'https://cdn.bootcss.com/jquery/3.2.1/jquery',则将id转化为'https://cdn.bootcss.com/jquery/3.2.1/jquery'。代码如下:
function parseAlias(id) { //如果有定义alias,将id替换为别名对应的地址 var alias = data.alias return alias && isString(alias[id]) ? alias[id] : id }
为依赖添加入口,方便追根溯源
resolve之后获得uri,通过uri进行Module的实例化,然后调用pass方法,这个方法主要是记录入口模块到底有多少个未加载的依赖项,存入到remain中,并将entry都存入到依赖模块的_entry属性中,方便回溯。而这个remain用于计数,最后onload的模块数与remain相等就激活entry模块的回调。具体代码如下(代码经过精简):
Module.prototype.pass = function() { var mod = this var len = mod.dependencies.length // 遍历入口模块的_entry属性,这个属性一般只有一个值,就是它本身 // 具体可以回去看use方法 -> mod._entry.push(mod) for (var i = 0; i < mod._entry.length; i++) { var entry = mod._entry[i] // 获取入口模块 var count = 0 // 计数器,用于统计未进行加载的模块 for (var j = 0; j < len; j++) { var m = mod.deps[mod.dependencies[j]] //取出依赖的模块 // 如果模块未加载,并且在entry中未使用,将entry传递给依赖 if (m.status < STATUS.LOADED && !entry.history.hasOwnProperty(m.uri)) { entry.history[m.uri] = true // 在入口模块标识曾经加载过该依赖模块 count++ m._entry.push(entry) // 将入口模块存入依赖模块的_entry属性 } } // 如果未加载的依赖模块大于0 if (count > 0) { // 这里`count - 1`的原因也可以回去看use方法 -> mod.remain = 1 // remain的初始值就是1,表示默认就会有一个未加载的模块,所有需要减1 entry.remain += count - 1 // 如果有未加载的依赖项,则移除掉入口模块的entry mod._entry.shift() i-- } } }
如何发起请求,下载其他依赖模块?