requirejs的源码学习(02)——模块加载流程

目录

前言

回顾

程序入口

require.config的执行过程

require这个API加载模块的流程


前言

这里讨论的模块加载流程只讨论define定义,require获取的形式。

这里不涉及data-main以及config中的依赖模块加载过程。

回顾

requirejs的初始化流程:

扫描二维码关注公众号,回复: 13747696 查看本文章

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获得下载。

猜你喜欢

转载自blog.csdn.net/lengye7/article/details/123754723
今日推荐