Requirejs在项目中使用很多,平时配置好以后,只是在用,很少去探究其内部实现,只知道是通过script标签去加载依赖,监听加载成功或失败的事件,再去继续逐步加载。今天大致看了一下源码,发现其内部实现还是十分复杂的,看了一小部分,做个笔记。
AMD:Asynchronous Modules Definition异步模块定义,提供定义模块及异步加载该模块依赖的机制。
版本 RequireJS 2.3.2
文件结构
新建几个最简单的示例文件,后面的推理以这几个文件为准
主入口页<script src="require.js"></script>
<script>
requirejs({
baseUrl:'modules/',
})
require(['main'],function(main){
main.init();
})
</script>
main.jsdefine(['./a'],function(a){
return {
init(){
a.init();
console.log('Main is init!')
}
}
})
a.jsdefine(function(){
return {
init:function(){
console.log('A is init');
}
}
})
初始化过程
从页面初始化到main模块还没有加载之前,源码里就已经执行了四次 requiresjs()
方法
req({}) => requirejs()
初始化上下文对象req(cfg) => requirejs()
初始化默认的配置requirejs({cusConfig}) => requirejs()
初始化用户自定义的配置require([main]) => requirejs()
初始化入口模块
1761 req = requirejs = function (deps, callback, errback, optional) {) |
从这一行可以看出 requier
和 requirejs
是相同的
也就是 require('a')
会走 requirejs
, 同时会创建一个模块映射对象
所以上面四次 requirejs()
的执行创建了 四个模块映射对象,同时检查依赖
前面三个模块由于不是异步模块,所以不用load
到第四个模块,检测到有依赖模块 main
,于是创建了一个 main
的模块映射,并初始化了这个模块
依赖加载过程
创建 script
标签,加载 main.js
这个模块,并检查当前模块依赖是否加载完成
如果 main.js
模块已经加载完成,那么进入 define()
方法,开始解析 main.js
模块:
- 创建 main 模块映射
- 初始化 main 模块
- 处理 main 模块的依赖,发现有依赖 a 模块
- 创建 a 模块的映射
- 初始化 a 模块
- 创建 a 模块的
script
标签加载 a 模块 - 检查依赖是否加载完成
如果 a.js
模块加载完成,进入 define()
方法,开始解析 a.js
模块:
- 创建 a 模块映射
- 初始化 a 模块
- 处理依赖,发现没有依赖
- 检查主模块及其依赖的所有模块是否已经加载完成
- 所有依赖加载完成,从队列中依次执行各个模块注册的回调并传递相应的依赖
在调用的过程中,主要调用的方法就是 requirejs()
与 define()
这两个方法,下面看一下这两个方法的内部实现。
requiresjs()
这个方法作用就是初始化上下文和加载模块
req = requirejs = function (deps, callback, errback, optional) { |
context.require()
最终会执行这个方法链 nextTick() => module.init() => enable() => check() => fetch() => load()
context.nextTick()下面会说到
define()
用于定义模块,将模块推入到一个队列中,用于Module的初始化
define = function (name, deps, callback) { |
context.nextTick()
requirejs() 方法最终会调用这个方法,并延迟4s执行, 放在一个setTimeout中,用于加载依赖模块。
//确保所有的依赖都加载完成 |
intakeDefines
对队列中的模块做初始化
function intakeDefines() { |
load
上面 从 requireMod.init()
开始,就会调用 enable()=>check()=>fetch()=>load(),到 req.load
这里,终于可以创建script标签到head中了
req.load = function (context, moduleName, url) { |
然后调用 checkLoaded()
方法去检测是否加载成功,成功之后会触发上面注册的 context.onScriptLoad
事件,然后触发 completeLoad
事件,这里加载回来的模块也有define()
方法,那么又会调用 define()
方法注册模块,分析依赖并加依赖,依赖加载成功后,检测依赖是否还有依赖,再看全局的依赖是否全部加载完成,然后才依次触发队列里的callback回调,并将依赖模块对象传递给回调使用。
后记
花了一天的时间,终于把大致逻辑理清楚了一点,不得不说RequireJS实在太复杂了,可能是全局变量太多,依赖检查、异步加载、回调队列、事件等都有大量运用,今天也只是看了个大概,后面决定实现个简单版的requirejs再来体验一下它的精髓。