目录
前言
这里讨论的模块加载流程只讨论define定义,require获取的形式。
这里不涉及data-main以及config中的依赖模块加载过程。
回顾
requirejs的初始化流程:
1、初始化各种API以及功能函数
1)、req=require=requirejs,req.config,req.load,define等。
2、newContext这个核心
1)、定义各种内部使用的数据结构
2)、context这个中心数据结构
3)、module这个基本操作单元
3、完成对require的配置
程序入口
还是上次那个程序入口,这里再贴一次。
<!--我在这里并不使用data-main方式,具体原因参考官网-->
<script src="require.js" type="text/javascript"></script>
<script>
require.config({
baseUrl:"js",
paths:{
app:"./app",
}
});
require(["app"],function(){
console.log("This is index.html,require app success!");
});
</script>
require.config的执行过程
直接跳过初始化过程,来到require.config。
req.config = function (config) {
return req(config);
};
require.config实际调用的是req(config),所以这里直接来到了req内部。
最后通过context.configure(config)完成对默认配置项的修改。
注意:如果在config中添加deps相关配置,则require.config的流程会包含对deps中依赖项的加载,我这里并没有添加deps配置,所以流程及其简单。
require这个API加载模块的流程
执行完require.config,继续执行就来到requie加载app这个模块的部分了。
require(["app"],function(){
console.log("This is index.html,require app success!");
});
进入require内部之后,来到context.require(前面一篇说过,这个函数是模块加载的核心入口)。
跟进去
先说intakeDefines,由于这里是第一个被加载的模块,所以目前来说,globalDefQueue中没有任何元素,因此它实际上没有做什么实质性的事情。
然后,通过context.nextTick创建了一个模块加载任务,这个加载任务 ,在下一次事件循环中被执行,加载deps中包含的模块,也就是app。
注意点:
在调用require之前,在初始化过程中,调用了两次req(),由于这两次调用的参数都是空对象,所以不会执行context.configure,因此,最后只调用了context.require,于是最终会产生两次空的模块加载任务。
再之后,执行require.config,实际上还是执行req(config),这一次config不为空对象,所以会调用一次context.configure,在configure内部,由于cfg.deps为空(config中没配置deps这个配置项),不会调用context.require;最后,在req末尾调用了一次context.require,产生了一次空的模块加载任务。
于是,在我们调用require之前,总共产生了三次空的模块任务,因此只有第四次模块加载任务才是app对应的模块任务。
这个空的调用,在我之前那一篇文章中就吐槽过了,平白无故多了3次没卵用的任务。
执行到这里,require执行结束,然后就会进入下一次事件循环,于是模块加载任务被执行。
这里已经进入到模块加载任务内部执行了。
由于globalDefQueue还是一个空队列,所以intakeDefines没有任何作用。
然后执行进入getModule(makeModuleMap(null,relMap)),这是非常重要的一个环节,这里做了比较多的工作。
先来看makeModule这个函数
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
var url, pluginModule, suffix, nameParts,
prefix = null,
parentName = parentModuleMap ? parentModuleMap.name : null,
originalName = name,
isDefine = true,
normalizedName = '';
//If no name, then it means it is a require call, generate an
//internal name.
if (!name) {
isDefine = false;
name = '_@r' + (requireCounter += 1);
}
nameParts = splitPrefix(name);
prefix = nameParts[0];
name = nameParts[1];
if (prefix) {
prefix = normalize(prefix, parentName, applyMap);
pluginModule = getOwn(defined, prefix);
}
//Account for relative paths if there is a base name.
if (name) {
if (prefix) {
if (isNormalized) {
normalizedName = name;
} else if (pluginModule && pluginModule.normalize) {
//Plugin is loaded, use its normalize method.
normalizedName = pluginModule.normalize(name, function (name) {
return normalize(name, parentName, applyMap);
});
} else {
// If nested plugin references, then do not try to
// normalize, as it will not normalize correctly. This
// places a restriction on resourceIds, and the longer
// term solution is not to normalize until plugins are
// loaded and all normalizations to allow for async
// loading of a loader plugin. But for now, fixes the
// common uses. Details in #1131
normalizedName = name.indexOf('!') === -1 ?
normalize(name, parentName, applyMap) :
name;
}
} else {
//A regular module.
normalizedName = normalize(name, parentName, applyMap);
//Normalized name may be a plugin ID due to map config
//application in normalize. The map config values must
//already be normalized, so do not need to redo that part.
nameParts = splitPrefix(normalizedName);
prefix = nameParts[0];
normalizedName = nameParts[1];
isNormalized = true;
url = context.nameToUrl(normalizedName);
}
}
//If the id is a plugin id that cannot be determined if it needs
//normalization, stamp it with a unique ID so two matching relative
//ids that may conflict can be separate.
suffix = prefix && !pluginModule && !isNormalized ?
'_unnormalized' + (unnormalizedCounter += 1) :
'';
return {
prefix: prefix,
name: normalizedName,
parentMap: parentModuleMap,
unnormalized: !!suffix,
url: url,
originalName: originalName,
isDefine: isDefine,
id: (prefix ?
prefix + '!' + normalizedName :
normalizedName) + suffix
};
}
这个函数实际上最后就是生成个模块相关参数信息。
最后app的那一次模块加载任务生成的moduleMap信息如下:
然后接下来会调用getModule,参数信息就是上面生成的moduleMap:
这里的_@r5是模块加载入口,requirejs会为所有的模块入口都创建一个module对象。
提示:
registry保存的是已经注册过的模块id,对于已经加注册过的模块,自然重新生成module对象。
id:这是requirejs内部对module对象的唯一标识。
module:每个模块的抽象数据结构。
function getModule(depMap) {
var id = depMap.id,
mod = getOwn(registry, id);
if (!mod) {
mod = registry[id] = new context.Module(depMap);
}
return mod;
}
getModule先判断是否这个模块是已经加载注册过了,如果已经注册过,就直接返回对应的Module对象;如果没有注册过,就新生成一个Module对象,并在registry中注册。
随后调用init开始加载该模块。
注意点:
在这里生成的module对象对应的是当前调用require的模块,这里我们是在index.html中调用的,所以这个模块没有名字,requirejs会自动生成名字和id,名字或者id为_@r5。
所有的初始模块加载入口都没有名字,requirejs会将其视为一个模块,会为其创建一个module对象,这个初始的模块对象名由requirejs生成名字。
deps则是_@r5的依赖模块列表,在后面还会为deps中的每一个模块生成一个module对象。
另外:
enabled:true很重要,表示这个module可以被加载了。
在这里,module对象与依赖模块列表[app]关联起来了,如下所示。
因为requirejs的模块加载允许一次性加载多个依赖模块,所以这个deps是一个数组,这一步关联,只是将这一次需要加载的多个依赖模块数组与之前创建的module对象关联起来。
然后利用这个module对象,逐步对deps数组中的每一个模块进行操作。
后面,requirejs会遍历这个deps数组,为每个模块创建一个module对象,然后分别开始加载。
总之这里就挺绕,requirejs将index.html中调用require的代码也视为一个module,由于没有module名,所以requirejs自动生成一个。
然后在init的最后,调用了this.enable函数,跟进去这个函数看看。
enable这个函数内部会为这个模块的所有依赖模块创建module对象,然后分别加载。
箭头处遍历deps,为deps中的每一个模块都创建一个module对象,然后加载。
跟进去箭头处的each的回调函数:
这里为app模块创建一个moduleMap对象:
随后通过on这个函数,在on内部调用getModule函数,创建了一个app对应的module对象,并且在registry中注册。
然后这个回调函数执行到最后,调用了context.enable,而在context.enable中,最后又会调用到module.enable中来。
而此时this指向的是app所对应的那个module了,也就是说,通过app对应的module对象开始加载app了。
由于此时app还没有加载,所以还不知道app的deps,因此这里会直接执行到末尾调用this.check:
跟进去this.check看看这个是什么情况:
上图中可以看到,最后会调用this.fetch:
在this.fetch中又会调用this.load:
在this.load中调用context.load:
而context.load最终就会调用req.load,req.load在前面一篇中已经解析过了,这个函数用于创建script节点,真正载入脚本。
执行完之后,index.html中多了一行:
至此,app这个模块加载完成。
小总结:
1、通过require进入模块加载
2、进入localRequire中创建模块加载任务
3、执行模块加载任务
1)、为index.html中的require代码部分创建一个module对象,该module的名字由requirejs生成,这里用_@r5指代。
2)、把require中加载的模块(这里是app)作为上一步的(_@r5)对象的deps,然后调用(_@r5).init开始初始化过程。
3)、(_@r5).init之后,调用(_@r5).enable遍历deps中依赖项,然后加载deps中的依赖项。
4、deps中依赖项加载过程(这里由于依赖项中只有app,所以只加载了app就结束了。):
1)、在enable中执行,来到each函数,通过该函数来遍历deps,并对其中的每一个依赖项都调用相应的函数,由于deps中只有一个app,所以最终只有一个app作为相应的函数参数被调用。
2)、进入对应的函数中执行,首先会为app创建一个moduleMap对象,该对象中的有三个重要的properties:name,id,url,其中id是requirejs接下来在内部使用的模块唯一标识,url是最后用来创建script标签时的src来源,name是模块名。
3)、接着执行on函数(这个on函数是context中的那个,不是module中的那个,要区分清楚),在on函数内部,通过getModule调用返回app对应的module对象,接下来用app指代这个module对象。
4)、然后调用context.enable,进而调用到(app).enable中。
可见,module.enable是module加载模块的核心函数,模块的加载就在这个函数之中实现。
5)、最终,调用(app).check,然后调用(app).fetch,在然后调用(app).load,最终调用到context.load,而在context.load中调用到req.load,然后在这里完成了script标签的创建,于是app获得下载。