一步一步带你分析 requirejs
阅读原文时间:2023年07月14日阅读:1

详细源代码一共就2000多行,来看我这篇分析的同学应该都下载下来了,好了,话不多说,开始:

代码的开头就出现3个全局变量: requirejs, require, define

var requirejs, require, define;

(function(global, setTimeout){

balababla……

})(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)))

require 和 define 大家应该都知道上干什么的,说实话,我是不知道的,在分析代码的时候,我从来也没用过这个框架,就听过AMD,就来直接看源码了。

如果你也不是很清楚,这2个变量是干什么的,我就来简单介绍一下,懂得的同学要是发现我说错了,希望指点我也一下。

主页面 index.html:

注意src是引入我们的requirejs库,  data-main:就是我们第一次用requrie的地方:






 main.js:

这里2个代码块都是依赖require的:

(1)require.config:配置

(2)requrie(); 加载需要的函数,注意里面的 ['name', 'say'],其实都是文件名,它们都在./js/ 目录下,具体看conifg

require.config({
baseUrl: '',
paths: {
'nameDep': 'js/nameDep',
'say': 'js/say',
'name': 'js/name'
},
shim: {
'name': {
deps: ['nameDep']
}
}
});
require(['name', 'say'], function (name, say) {
say(name);
});

./js/name.js  和 ./js/say.js

//name
define([''], function () {
return '测试';
});

//say
define([], function () {
return function (name) {
console.log(name);
};
});

最后注意在config中有个skim,这里面也是定义js文件的,只是由于他可能不符合AMD加载的规范

./js/nameDep.js

console.log("nameDep.js")

以上分析,我们大概看得出  require 和 define 都是 function

--------------------------------------分割线-------------------------------------------------------------------------

下面进入2000多行匿名函数的讲解:

首先说一下2个参数: global 和 setTimeout

(function (global, setTimeout) {

})(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout))

如果在浏览器的环境中,就是平时我们所知的  windows对象 和 windows.setTimeout方法 。本文只讨论浏览器环境,别的环境咱不考虑。

接下来就是一大堆的变量和函数的定义,太多了!反正我就扫了一眼,记不住?无所谓,只需要记住这开头这3个变量就行了:req,contexts,cfg

2个是空对象  cfg,contexts ,还有一个函数 req 。

为什么它是函数??? 你一拉到底,一定会看到 req = requirejs = function(){} 的定义,眼神不好也没关系,看我下面的代码框里有req({});

var req,
….
contexts = {},
cfg = {},
….
//各种function定义
….

    //Create default context.  
  req({});

 .....

好了把眼神定位到 req({})吧, 因为这是上面一大堆定义后,第一次执行了代码!!!!

req这个函数长什么样子?? 你用文字匹配往上找:

/**
* Main entry point.
*
* If the only argument to require is a string, then the module that
* is represented by that string is fetched for the appropriate context.
*
* If the first argument is an array, then it will be treated as an array
* of dependency string names to fetch. An optional function callback can
* be specified to execute when all of those dependencies are available.
*
* Make a local req variable to help Caja compliance (it assumes things
* on a require that are not standardized), and to give a short
* name for minification/local scope use.
*/
req = requirejs = function (deps, callback, errback, optional) {

    //Find the right context, use default  
    var context, config,  
        contextName = defContextName; //开头定义了 defContextName = "\_"

    // Determine if have config object in the call.  
    if (!isArray(deps) && typeof deps !== 'string') {  
        // deps is a config object  
        config = deps;  
        if (isArray(callback)) {  
            // Adjust args if there are dependencies  
            deps = callback;  
            callback = errback;  
            errback = optional;  
        } else {  
            deps = \[\];  
        }  
    }

       //第一次没有config.context 跳过  
    if (config && config.context) {  
        contextName = config.context;  
    }

       //第一次context == undefined  
    context = getOwn(contexts, contextName);

       //第一次进入  
    if (!context) {

        //只有第一次会调用newContext("\_")  
        context = contexts\[contextName\] = req.s.newContext(contextName);

    }

    if (config) {  
        context.configure(config);  
    }

    return context.require(deps, callback, errback);  
};

上面代码  context出现的频率非常高,说明这小子很重要。那我们来看看这小子到底是啥!!!

直接找到它的定义:

context = contexts[contextName] = req.s.newContext(contextName);

那 newContext 又是什么鬼! 搜它!!!!!

function newContext(contextName) {

   var context  
      ...  
   //一大堆function定义  
     ....  
    context={  
          .....          

     }  
    context.require = context.makeRequire();  
    return context;  
}

用20秒的时间扫一眼!老套路,一大堆的变量,函数的定义。 一直到最后return 一个 context变量。

再往上拉看看 context 到底是个什么鬼!!

记忆力没毛病的话,发现context对象里装的全是刚才定义过一些变量,函数。

-----------------------------------------------------------------------------------------------

|    其中return的上一行代码:                                                                   |

|              context.require = context.makeRequire();                                  |

|      有兴趣就去看一眼,很简单,就是返回一个叫localRequire的闭包     |

------------------------------------------------------------------------------------------------

OK,这个newContext函数我们已经差不多了解了,就是返回一个对象,通过这个对象控制newContext里的一系列私有变量和对象。

我们的contexts就如下图所示:

接下去走

context.configure(config);

我建议你去看一下,因为里面啥也没干,你只需要花10秒左右的时间就可以看完。。。

然后走

return context.require(deps, callback, errback);

看上面的图,context.require 就是 localRequire

接下来,我们走进localRequire的世界里一探究竟!!!!其中deps = [ ]

function localRequire(deps, callback, errback) {
var id, map, requireMod;

                if (options.enableBuildCallback && callback && isFunction(callback)) {  
                    callback.\_\_requireJsBuild = true;  
                }

                if (typeof deps === 'string') {  
                    if (isFunction(callback)) {  
                        //Invalid call  
                        return onError(makeError('requireargs', 'Invalid require call'), errback);  
                    }

                    //If require|exports|module are requested, get the  
                    //value for them from the special handlers. Caveat:  
                    //this only works while module is being defined.  
                    if (relMap && hasProp(handlers, deps)) {  
                        return handlers\[deps\](registry\[relMap.id\]);  
                    }

                    //Synchronous access to one module. If require.get is  
                    //available (as in the Node adapter), prefer that.  
                    if (req.get) {  
                        return req.get(context, deps, relMap, localRequire);  
                    }

                    //Normalize module name, if it contains . or ..  
                    map = makeModuleMap(deps, relMap, false, true);  
                    id = map.id;

                    if (!hasProp(defined, id)) {  
                        return onError(makeError('notloaded', 'Module name "' +  
                                    id +  
                                    '" has not been loaded yet for context: ' +  
                                    contextName +  
                                    (relMap ? '' : '. Use require(\[\])')));  
                    }  
                    return defined\[id\];  
                }

                //Grab defines waiting in the global queue.  
                intakeDefines();

                //Mark all the dependencies as needing to be loaded.  
                context.nextTick(function () {  
                    //Some defines could have been added since the  
                    //require call, collect them.  
                    intakeDefines();

                    requireMod = getModule(makeModuleMap(null, relMap));

                    //Store if map config should be applied to this require  
                    //call for dependencies.  
                    requireMod.skipMap = options.skipMap;

                    requireMod.init(deps, callback, errback, {  
                        enabled: true  
                    });

                    checkLoaded();  
                });

                return localRequire;  
            }

很快我们就把目光锁定到(因为,前面的一大段代码都是if语句,不符合条件进不去)

  //Grab defines waiting in the global queue.  
                intakeDefines();

intakeDefines --》takeGlobalQueue--》context.nextTick--》intakeDefines --》  requireMod.init --》  checkLoaded();

里面大致就是上面所示,因为有点复杂,我这里暂时先不说,接续走简单的。。

到此为止,我们已经走完了一边req。

大致流程如下图所示:

接下去,我们再一拉到底!!!!

倒数第二行:

//Set up with config info.  
req(cfg);

cfg还记得是什么吗?? 一开始是定义为空对象啊!!!

有地方修改过它吗? 往上拉一拉!!

//Look for a data-main script attribute, which could also adjust the baseUrl.
if (isBrowser && !cfg.skipDataMain) {
//Figure out baseUrl. Get it from the script tag with require.js in it.
eachReverse(scripts(), function(script) {
//Set the 'head' where we can append children by
//using the script's parent.
if (!head) {
head = script.parentNode;
}

  //Look for a data-main attribute to set main script for the page  
  //to load. If it is there, the path to data main becomes the  
  //baseUrl, if it is not already set.  
  dataMain = script.getAttribute('data-main');  
  if (dataMain) {  
    //Preserve dataMain in case it is a path (i.e. contains '?')  
    mainScript = dataMain;

    //Set final baseUrl if there is not already an explicit one,  
    //but only do so if the data-main value is not a loader plugin  
    //module ID.  
    if (!cfg.baseUrl && mainScript.indexOf('!') === -1) {

      //Pull off the directory of data-main for use as the  
      //baseUrl.  
      src = mainScript.split('/');

      mainScript = src.pop();

      subPath = src.length ? src.join('/') + '/' : './';

      cfg.baseUrl = subPath;  
    }

    //Strip off any trailing .js since mainScript is now  
    //like a module name.  
    mainScript = mainScript.replace(jsSuffixRegExp, '');

    //If mainScript is still a path, fall back to dataMain  
    if (req.jsExtRegExp.test(mainScript)) {

      mainScript = dataMain;  
    }

    //Put the data-main script in the files to load.  
    cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : \[mainScript\];

    return true;  
  }  
});  

}

这里面做的事情很简单,就是找到页面中包含 "data-main" 属性的script标签,分析路径。

最后得到这样的结果:

好了。再回到req(cfg);

这一次我们好好的走一走上面流程图里的步骤。。。。

把目光迅速锁定到:

return context.require(deps, callback, errback);

 

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器