总的来说pass方法就是记录了remain的数值,接下来就是重头戏了,调用所有依赖项的fetch方法,然后进行依赖模块的加载。调用fetch方法的时候会传入一个requestCache对象,该对象用来缓存所有依赖模块的request方法。
var requestCache = {} for (i = 0; i < len; i++) { m = cachedMods[uris[i]] // 获取之前实例化的模块对象 m.fetch(requestCache) // 进行fetch } Module.prototype.fetch = function(requestCache) { var mod = this var uri = mod.uri mod.status = STATUS.FETCHING callbackList[requestUri] = [mod] emit("request", emitData = { // 设置加载script时的一些数据 uri: uri, requestUri: requestUri, onRequest: onRequest, charset: isFunction(data.charset) ? data.charset(requestUri) : data.charset, crossorigin: isFunction(data.crossorigin) ? data.crossorigin(requestUri) : data.crossorigin }) if (!emitData.requested) { //发送请求加载js文件 requestCache[emitData.requestUri] = sendRequest } function sendRequest() { // 被request方法,最终会调用 seajs.request seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset, emitData.crossorigin) } function onRequest(error) { //模块加载完毕的回调 var m, mods = callbackList[requestUri] delete callbackList[requestUri] // 保存元数据到匿名模块,uri为请求js的uri if (anonymousMeta) { Module.save(uri, anonymousMeta) anonymousMeta = null } while ((m = mods.shift())) { // When 404 occurs, the params error will be true if(error === true) { m.error() } else { m.load() } } } }
经过fetch操作后,能够得到一个requestCache对象,该对象缓存了模块的加载方法,从上面代码就能看到,该方法最后调用的是seajs.request方法,并且传入了一个onRequest回调。
for (var requestUri in requestCache) { requestCache[requestUri]() //调用 seajs.request } //用来加载js脚本的方法 seajs.request = request function request(url, callback, charset, crossorigin) { var node = doc.createElement("script") addOnload(node, callback, url) node.async = true //异步加载 node.src = url head.appendChild(node) } function addOnload(node, callback, url) { node.onload = onload node.onerror = function() { emit("error", { uri: url, node: node }) onload(true) } function onload(error) { node.onload = node.onerror = node.onreadystatechange = null // 脚本加载完毕的回调 callback(error) } }
通知入口模块
上面就是request的逻辑,只不过删除了一些兼容代码,其实原理很简单,和requirejs一样,都是创建script标签,绑定onload事件,然后插入head中。在onload事件发生时,会调用之前fetch定义的onRequest方法,该方法最后会调用load方法。没错这个load方法又出现了,那么依赖模块调用和入口模块调用有什么区别呢,主要体现在下面代码中:
if (mod._entry.length) { mod.onload() return }
如果这个依赖模块没有另外的依赖模块,那么他的entry就会存在,然后调用onload模块,但是如果这个代码中有define方法,并且还有其他依赖项,就会走上面那么逻辑,遍历依赖项,转换uri,调用fetch巴拉巴拉。这个后面再看,先看看onload会做什么。
Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED for (var i = 0, len = (mod._entry || []).length; i < len; i++) { var entry = mod._entry[i] // 每次加载完毕一个依赖模块,remain就-1 // 直到remain为0,就表示所有依赖模块加载完毕 if (--entry.remain === 0) { // 最后就会调用entry的callback方法 // 这就是前面为什么要给每个依赖模块存入entry entry.callback() } } delete mod._entry }
依赖模块执行,完成全部操作
还记得最开始use方法中给入口模块设置callback方法吗,没错,兜兜转转我们又回到了起点。
mod.callback = function() { //设置模块加载完毕的回调 var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { // 执行所有依赖模块的exec方法,存入exports数组 exports[i] = cachedMods[uris[i]].exec() } if (callback) { callback.apply(global, exports) //执行回调 } // 移除一些属性 delete mod.callback delete mod.history delete mod.remain delete mod._entry }
那么这个exec到底做了什么呢?
Module.prototype.exec = function () { var mod = this mod.status = STATUS.EXECUTING if (mod._entry && !mod._entry.length) { delete mod._entry } function require(id) { var m = mod.deps[id] return m.exec() } var factory = mod.factory // 调用define定义的回调 // 传入commonjs相关三个参数: require, module.exports, module var exports = factory.call(mod.exports = {}, require, mod.exports, mod) if (exports === undefined) { exports = mod.exports //如果函数没有返回值,就取mod.exports } mod.exports = exports mod.status = STATUS.EXECUTED return mod.exports // 返回模块的exports }
这里的factory就是依赖模块define中定义的回调函数,例如我们加载的main.js中,定义了一个模块。
define(function (require, exports, module) { module.exports = 'main-module' })