requirejs笔记
该文记录requirejs对AMD规范的实现方式,主要涉及加载依赖,模块注入,模块执行的分析,对于路径分析、插件执行、模块命名规范等未做深入理解,以理解require实现思路为目标。
AMD规范
AMD是对于js模块化编程的一种规范,即向外暴漏两个函数define和require,分别实现定义模块和使用模块,实现模块化js编码,API如下:
定义模块:define(id?, dependencies?, factory);
使用模块:require(String)|require(Array, Function)
DEMO
模块定义:
//1.js
define(function() {
var word;
var setWord = function(w) {
word = w;
}
var getWord = function() {
return word;
}
var say = function() {
console.log("in 1.js ,i am say:" + word);
}
return {
setWord: setWord,
say: say,
getWord: getWord
}
});
//2.js
define(["1"], function(a) {
a.plugin = function() {
console.log("this is add plugin in 2.js");
}
return a;
});
//3.js
define(["2", "1"], function(b) {
var sayhi = function() {
b.setWord("in 3.js,3 base 2");
b.say();
b.plugin();
var a=require("1");
a.say();
}
return {
sayhi: sayhi
}
});
模块调用:
//main.js
require(['3'], function(a,c) {
// a.setWord("set a from main");
// a.say();
a.sayhi();
});
html:
<script type="text/javascript" src="require.js" data-main="js/main"></script>
代码结构
代码通过全局变量向外暴漏require和define两个对象(即函数),变量的实现部分在自执行函数中防止变量污染,内部的对象关系如下: req=requirejs>context>module,对象嵌套。
代码调用堆栈 requirejs>newContext+context.makeRequire=localRequire>context.nextTick>getModule+module.init>module.enable>module.check>module.fetch>module.load>context.load
onScriptLoaded>completeLoaded>callGetModule>moduel.init>module.enable>moudle.check>module.emit(‘defined’)->fatherModule.check>fa-fatherModule.check…>rootModule.check,run-callbacks depends depCount;
module.check为关键函数,会被反复调用用于检验模块加载情况,其功能包含依赖下载,错误校验,模块执行。
执行逻辑
requirejs源码中以require作为代码执行入口,其中包括初始化执行上下文环境、启动依赖加载。当每调用一次require(),就是初始化并执行一个独立模块,其中主要包含该模块的依赖分析,依赖加载,依赖加载执行完成的模块执行。
requirejs中所有的require调用都会生成一个根模块,其依赖可理解为子模块,当所有子模块加载完成以后注入依赖,执行根模块的回调,即require(factory)中的factory方法,完成模块的启动。
requirejs执行逻辑部分
- 启动加载
- 依赖分析,初始化依赖模块
- 依赖加载
- 依赖加载成功,补充定义该模块,依赖再分析+初始化依赖模块
- 依赖再加载
- 依赖加载成功,补充定义该模块,依赖再分析+初始化依赖模块
- 依赖再加载 ….
- 无依赖模块加载成功(暂不讨论循环依赖)
- 模块上溯回调,父模块完成加载,继续上溯回调 ….
- 直至上溯到根模块,完成requirejs功能
DEMO代码分析
- requirejs中调用req({})初始化上下文后生成Context对象
- req(config),创建_@r4根模块,启动加载js/main文件
- main.js文件加载完成以后执行其中的require(..),将会创建_@r5根模块
- 执行main.js的onScriptLoaded方法,此处作用不大,_@r4根模块作用完成
- 因为require中存在异步,此时开始执行异步,main中的require创建_@r5根模块,并创建、加载依赖模块3
- 3模块完成下载,并执行define函数,将该模块定义包括依赖一起放入数组中,并置入全局队列globalDefQueue变量中,等待3模块加载完成的onScriptLoaded回调
- 3模块的onScriptLoaded开始回调,首先从globalDefQueue获取3模块的定义和依赖,然后调用module.init函数,添加标记,继续分析依赖为1和2,遍历创建1和2的module的对象,调用各自的check去加载,此时3模块的调用等到了1、2加载的出栈,因为没有完成依赖加载,所以完成check方法后并没有其他的操作;
- 此时1完成了加载,执行第6步,依赖分析中没有依赖,所以在check中完成模块的定义,并通过module.emit(“defined”)事件,回调依赖该模块的模块;此时会有_@r5模块收到事件,然后响应,因为其中的依赖计数为depCount中还有2号依赖没有加载初始化完,所以不做操作;
- 2模块早已加载完成,在回调队里中,等1的回调完成以后,2的onScriptLoaded开始回调开始执行,跟第7步一样,依赖分析存在1,但是发现1已经完成加载和初始化,直接取1的模块值注入到2中,调用module.check方法完成2模块的加载和初始化,此时调用module.emit(“defined”),_@r5模块收到事件,通过依赖计数发现依赖全部加载完成,注入依赖并执行main中requir中的factory方法,完成模块话编程
- ps:如果2先于1加载完成,因为2依赖1,所以不会再创建1,因为1已经创建完成,只是2会添加对1加载完成的监听,当1完成,不管先响应2还是3,2在完成的时候也会响应一下3,都可以实现该依赖逻辑的正常执行,达到最后目的
以上为demo案例中的逻辑分析,具体可见源代码。其中理解管家的函数为onScriptLoaded和on方法,一个加载,一个上溯回调;通过在module中添加一些状态标记来进行功能逻辑控制,状态依次为:init-enabled-enabling-defining-defined
后续学习
进行循环依赖的分析以及路径、命名规范分析的实验
理解AMD规范,思考其他实现方式
多场景状况实验,包括慢网速、插件部分等
参考资料
http://efe.baidu.com/blog/dissecting-amd-what/
总结
requirejs中通过创建根模块实现模块加载的启动,在根模块中通过链表管理子模块,通过依赖计数和闭包实现子模块加载完成上溯回调。
备注:
newContext创建内部对象context,作为执行上下文,该方法只执行一次,context对象属性:
- Module: (map)
- completeLoad: (moduleName)
- config: Object
- configure: (cfg)
- contextName: “_”
- defQueue: Array[0]
- defined: Object
- enable: (depMap)
- execCb: (name, callback, args, exports)
- load: (id, url)
- makeModuleMap: makeModuleMap(name, parentModuleMap, isNormalized, applyMap)
- makeRequire: (relMap, options)
- makeShimExports: (value)
- nameToUrl: (moduleName, ext, skipExt)
- nextTick: (fn)
- onError: onError(err, errback)
- onScriptError: (evt)
- onScriptLoad: (evt)
- registry: Object
- require: localRequire(deps, callback, errback)
- urlFetched:Object
一个module对象记录了一个js文件的所有信息,包含依赖文件的信息以及文件导出文件信息的数据, Module对象属性:
- defineEmitted: true
- defined: true
- defining: false
- depCount: 0
- depExports: Array[1]
- depMaps: Array[1]
- depMatched: Array[1]
- enabled: true
- enabling: false
- errback: undefined
- events: Object
- exports: Object
- factory: ($)
- fetched: true
- ignore: undefined
- inited: true
- map: Object
- pluginMaps: Object
- shim: false