当前位置 : 主页 > 网页制作 > Dojo >

dojo 加载器源文件讲解

来源:互联网 收集:自由互联 发布时间:2021-06-15
之前写的都是Dojo的基本使用教程,整体的讲解了Dojo AMD加载器使用及原理, DOM 操作, 事件,动画,这些都是平常工作中会经常用到的, 但对于真正的提升还是要分析源代码,了解整个

之前写的都是Dojo的基本使用教程,整体的讲解了Dojo  AMD加载器使用及原理, DOM 操作, 事件,动画,这些都是平常工作中会经常用到的, 但对于真正的提升还是要分析源代码,了解整个的框架设计,同时针对每个函数或每行代码,看看大师们是怎么写的,有助于自己提写自己的编码水平。 

我们先从 dojo 包中 dojo.js 开始讲解。

概述

 dojo.js 这个文件是 dojo 的加载器,实现模块的定义与加载,主要暴露两个函数 define 和 require, 关于他的使用,在讲解源代码之前,你最好先了解 AMD模块定义, Dojo 加载器高级使用, 及 dojo 加载器参考文档.
加载器的源代码由以下几个部分组成, 也是整个加载器的开发路线图,之后主要依据这个路线图来做开发:
  1.  整个Dojo的初始化
  2. 小型库代码,提供基础的公用函数
  3. 定义has.js API, loader从头到尾有很多特征检测,都需要使用到这个API
  4. 定义 nodejs 和 rhino 侦测
  5. 定义 加载器使用到的数据
  6. 定义配置管理机制,如何将对userConfig, defaultConfig 以及其它的配置进行管理
  7. 定义对script元素的嗅探机制(CDN), 及以script 角本中的 data-dojoj-config中的数据嗅探。
  8. 根据用户提供的配置, defaultConfig, 以及第6步嗅探到的数据, 然后通过第5步的配置机制,来对loader进行整体的配置。
  9. 定义全局的 require 函数
  10. 定义模块解析机制, 如何根据模块id,解析为实际路径, 运行模块的工厂函数。
  11. 模块和插件模块定义机制( define 函数)
  12. 定义角本注入机制
  13. 定义 window加载侦测(dom, domReady)
  14. 定义日志API(功能)
  15. 定义跟踪API.
  16. 实现AMD规范中需要定义的函数
  17. 定义 dojo v1.x provide/require的机制,实现兼容之前版本的模块定义和请求
  18. 发布全局变量

初始化

初始函数是一个匿名闭包函数,接受两个参数,一个是用户指定的配置userConfig,  一个用默认的配置。  以下是提取出来的代码,最重要的是注释。
(function(userConfig, defaultConfig){

    
        //主体代码

})(
     // 传入用户配置
        function(){
            return this.dojoConfig || this.djConfig || this.require || {};
        },
    //传入默认配置
    {
        // 传入浏览器环境参数(通过has.js特征检测确定运行环境), 在其它环境,如nodejs时,会修改这些配置, 大部分选项都是用于build做优化的,这里有详细介绍has选项,http://dojotoolkit.org/reference-guide/1.9/dojo/has.html
        hasCache:{
            "host-browser": 1, //浏览器环境下使用dojo
            "dom":1,  //支持浏览器文档对像
            "dojo-amd-factory-scan":1, // loader选项,不是has.add 特征检测,默认情况是CommonJs的模块请求 define(["require","export","module",function(require,export,module){}), 如果指定为true,使用AMD扫描factory, 会自动设置这三个模块。即直接定义 define([],function(){})
            "dojo-loader":1, //表明使用dojo loader, 而不是requirejs等三方的。在_base/loader.js会使用它个选项来判断加载器是不是dojo 本身加载器,如果是第三方的,则无法加载老版本的Dojo.
            "dojo-has-api":1, //应用在has.js里, 主用于 has.js 里检测是否之前已经定义了has, has.add方法,如果使用了  dojo-loader,那么loader已经定义了这两个方法,在has.js里无需在定义这两个方法。如果使用第三方的加载器,则 dojo-has-api 默认为没有定义,那么在has.js里会重新定义
            "dojo-inject-api":1, // 应用在dojo.js, 表示是否支持跨域加载模块。
            "dojo-timeout-api":1, // 应用在dojo.js, 配合waitSeconds, 提供一个加载超时的API. 如是不设这个为true, 那么waitSeconds 也是无效的,因为没有一个计时器的功能。
            "dojo-log-api":1, // dojo.js,  主要用于提供日志功能,req.log('a','b'), 会调用console.log函数,并且把每个参数按行来输出。arguments[0], arguments[1]。
            "dojo-dom-ready-api":1, //dojo.js 提供一个监听器,当dom加载完成时,会通知这个监听器。
            "dojo-publish-privates":1, // dojo.js, 是否允许公开loader 的私有方法或者变量。 build时设置为 0
            "dojo-config-api":1, //dojo.js 确保在 dojo在 build时可配置
            "dojo-sniff":1, //dojo.js,允许扫描dojo.js 角本中的 data-dojo-config 和 djConfig 标签
            "dojo-sync-loader":1, // dojo.js ,是否可以使用老版本的加载器
            "dojo-test-sniff":1, // dojo.js, 是否需要侦测 Doh(单元测试)模块的测试配置 testConfig, build时为0
            "config-deferredInstrumentation":1, // deferred.js, 加载dojo/promise/instrumentation模块. 该模块用于监测被拒绝的承诺,将末被处理的错误输出到控制台. build 设置为0
            "config-useDeferredInstrumentation":"report-unhandled-rejections", // dojo/promise/instrumentation.js, 只将末被处理的拒绝,输出到控制台。 如果为report-rejections, 输出所有被拒绝的Promise 的信息
            "config-tlmSiblingOfDojo":1 // dojo.js ,是否允许运行非标准化的模块名解析, 模块名解析就是根据字符串,解析为正确的模块路径

        },
        packages:[{
            // 如果在dojoConfig中没有设置baseURL, 引导程序会计算baseUrl的路径为dojo文件夹,即包含 dojo.js的文夹件
            name:'dojo',  // 例如, 加载 dojo/dom, dojo代表包名,包的地址为baseUrl的文件夹,模块的地址为 baserUrl/dom.js
            location:'.'
        },{
            name:'tests',
            location:'./tests'
        },{
            name:'dijit',
            location:'../dijit'
        },{
            name:'build',
            location:'../util/build'
        },{
            name:'doh',
            location:'../util/doh'
        },{
            name:'dojox',
            location:'../dojox'
        },{
            name:'demos',
            location:'../demos'
        }],
        trace:{
            // 在加载一个模块时,需要跟踪哪些信息, 先设置以下的项,在调用require.trace.on
            "loader-inject":0,  // 输出模块加入到应用程序时的信息
            "loader-define":0,
            "loader-exec-module":0,
            "loader-run-factory":0, //运行模块的factory时的信息
            "loader-finish-exec":0,
            "loader-define-module":0,
            "loader-circular-dependency":0,
            "loader-define-nonmodule":0
        },
        asycn:0,  //默认为0, 以同步的方法加载,而不是AMD,这种加载会先把所有的基础模块如dojo/_base/kernel. 所以在用户配置里最好设置asycn:1. 这样就能按需加载。
        waitSeconds:15  //加载一个模块的时间为15 秒,如果模块没有在规定时间内加载完成,触发加载错误,这样Dojo就能知道如何处理,而不是一直等待


    }

)

小型库

  • noop -  空函数, 主要有三种应用,第一种需要初始化一个变量,传入一个noop引用(推荐使用,可以节省内存和代码),或者直接在变量后传入 funtion(){};  相当于声明一个函数娈量,这样在任何时该都能调用这个变量(空函数调用什么也不做), 满足某条件时在定义具体的功能(传入具体的函数)。如 var a=noop; a();  a=function(a){console.log('a')}; a('hello');   第二种是删除一个变量的功能,如 var a = function(){setTimeout(function(){console.log('a')}, 1000)};  a();  当你不想要这个计时器器功能时,可以 a=noop.   之后使用时还是可以调用 a函数,但不做任何时,如果 a=null. 你就不能把a作为一个函数来调用了。 第三种, 做为函数的传递值,比如回调函数中。你又没有具体要做的功能,你可以传递noop.
  • isEmpty - 检测一个对像是否为空
  • toString - 调用对像的toString方法。 用于输出一个对像的字符串值
  • isFunction - 检测一个对像是否为函数
  • isString - 检测一个对像是否为字符串
  • isArray - 检测一个对像是否为数组
  • forEach - 枚举对像可枚举的值,并对每个值进行操作
  • mix - 混合, 将一个对像的所有属性,复制到另一个对像。
  • makeError - 自定义一个error对像
  • uidSeed 和 uid -  每次调用require时,会得一个唯一标识符,格式为 _ 数字,如 require(config), uidSeed 为:_1 在调用require(['dom'], function(dom){}); uidSeed为: _2
  • req - 全局的require函数,这个函数本来应该定义在第9条 contextRequire。但接下来的has API需要使用到req函数, 将has方法,添加到req上, 即 require.has().
  • global - 引用当前环境的全局对像
  • document - 引用当前环境的文档对像
  • element - 通过 document 创建一个节点元素.

 // 实现一个内部会使用到的小型库, 提供基础的方法
         var noop=function(){

             },  // 空函数

             isEmpty=function(it){
                for(var p in it){
                    return 0;
                }
                 return 1;
             }, //对像是否为空

             toString={}.toString,

             isFunction = function(it){
                return toString.call(it) == "[object Function]";
             }, // 对像是否为函数

             isString = function(it){
                 return toString.call(it) == "[object String]";
             },

             isArray = function(it){
                 return toString.call(it) == "[object Array]";
             },

             forEach = function(vector, callback){
                 if(vector){
                     for(var i=0; i<vector.length;){
                         callback(vector[i++]);
                     }
                 }
             },

             mix = function(dist, src){
                 for(var p in src){
                     dist[p] = src[p];
                 }
                 return dist;
             },

             makeError = function(error, info){
                 return mix(new Error(error), {src:"dojoLoader", info: info});
             },

             uidSeed = 1,

             uid = function(){
                 return "_"+uidSeed++;
             },

             req = function(){

             };

             

has API

has API 主要是为 loader 提供了特征检测,当某个特征通过时,添加相应的代码,当某个特征末通过时删除相应的代码。 如果是采用第三方的 加载器,dojo toolkit会提供dojo/has.js 模块,并实现has.js 约定中的方法。 has API主要有以下几个变量和方法。
global -  引用当前的环境,作为测试函数的第一个参数传递
doc - 当前文档对像, 作为测试函数的第二个参数传递
element - 创建一个元素,作为测试函数的第三个参数传递
has -  方法, 作为特征检测主要的方法
has.add - 主法,添加一个特征,及测试。 并返回一个测试的值或者函数
       global= this,

             doc = global.document,

             element = doc && doc.createElement("Div"),

             has = req.has = function(name){  // 添加 has方法, 并将方法附加到 require 上。 内部可以直接通过 has(" feature name" )  外部可以通过 require.has(" feature Name")
                 return isFunction(hasCache[name]) ? hasCache[name] = hasCache[name](global, doc, element) : hasCache[name];  // 如果hasCach缓存的是一个测试函数,则调用这个测试函数,并将结果hasCache对应的项,如果不是,则直接返回相应的值
             },

             hasCache = has.cache = defaultConfig.hasCache;

    has.add = function(name, test, now,force){
       (hasCache[name] === undefined || force) && (hasCache[name] = test);   //整个表达式是的意思是: 如果没有定义name的特征或者强制覆盖,则进行赋值。 第一个括号用于两个表达式的共同的结果。
        return now && has(name); //当指定了now时,立即运行特侦测试,并返回结果。
    };


检测服务器环境

因为dojo加载器是支持 nodejs 和 rhino等服务器端的js 加载。所以先检测服务器特侦, 如果是相对于服务器环境,加载相应的配置文件,如果是对于前端开发, 可以跳过,或直接把这段给删除掉.
在检测了loader的使用环境后(浏览器, nodejs 或 rhino) . 需要将 userConfig中的 has测试 替换 defaultConfig的测试
    /*
     用于检测rhino环境
     */
    has.add("host-rhino", userConfig.has && "host-rhino" in userConfig.has ?
        userConfig.has["host-rhino"] :
        (typeof load == "function" && (typeof Packages == "function" || typeof Packages == "object")));
    if(has("host-rhino")){
        // owing to rhino's lame feature that hides the source of the script, give the user a way to specify the baseUrl...
        for(var baseUrl = userConfig.baseUrl || ".", arg, rhinoArgs = this.arguments, i = 0; i < rhinoArgs.length;){
            arg = (rhinoArgs[i++] + "").split("=");
            if(arg[0] == "baseUrl"){
                baseUrl = arg[1];
                break;
            }
        }
        load(baseUrl + "/_base/configRhino.js");
        rhinoDojoConfig(defaultConfig, baseUrl, rhinoArgs);
    }
    
        //loader使用环境后,用 userConfig中的has 替换defaultConfig指定的has测试
    for(var p in userConfig.has){
        has.add(p, userConfig.has[p], 0, 1); //强制替换
    }

定义加载器数据

loader 不仅需要提供require, define 和 has API等功能,还需要有数据或者变量来标识当前loader的工作状态及其它属性.  这些属性主要分成两部分,一是标识模块的加载状态,二为loader 的工作模式(同步,AMD模式,传统的跨域模式等,大部分是定义同步会使用到的变量,而我们可以仅考虑AMD加载, 即 async: 1 时的情境, 所以不必深究这段代码, 可以跳过,直接到loader的事件API),如下所示:
var requested = 1,  //请求已经发出的状态
        arrived = 2,    //请求的模块已经到达
        nonmodule = 3, //请求的不是一个模块
        executing = 4,   // 模块正在执行
        executed =5; // 模块执行完成

    if(has("dojo-trace-api")){
        // 如果在 hasCache有指定dojo-trace-api(用于调试追踪模块的加载情况, 输出相应的信息), 则把数字替换为更友好的文字
        requested = "requested";
        arrived = "arrived";
        nonmodule = "not-a-module";
        executing = "executing";
        executed = "executed";
    }
//  使用同步模式加载的代码, 这段代码可以不用理会
    var legacyMode = 0,
        sync = "sync",
        xd = "xd",
        syncExecStack = [],
        dojoRequirePlugin = [],
        checkDojoRequirePlugin = noop,
        transformToAmd = noop,
        getXhr;

    if(has("dojo-sync-loader")){
        req.isXdUrl = noop;

        req.initSyncLoader = function(dojoRequirePlugin_, checkDojoRequirePlugin_, transformToAmd_){
            // dojo/_base/loader.js 加载后会调用这个方法,来初始化同步加载器
            if(!dojoRequirePlugin){
                dojoRequirePlugin = dojoRequirePlugin_;
                checkDojoRequirePlugin = checkDojoRequirePlugin_;
                transformToAmd = transformToAmd_;
            }

            return {
                sync:sync,
                requested:requested,
                arrived:arrived,
                nonmodule:nonmodule,
                executing:executing,
                executed:executed,
                syncExecStack:syncExecStack,
                modules:modules,
                execQ:execQ,
                getModule:getModule,
                injectModule:injectModule,
                setArrived:setArrived,
                signal:signal,
                finishExec:finishExec,
                execModule:execModule,
                dojoRequirePlugin:dojoRequirePlugin,
                getLegacyMode:function(){return legacyMode;},
                guardCheckComplete:guardCheckComplete
            };
        };

        if(has("dom")){
            // 如果采用传统的同步加载器方式, loader需要定义一个小型的Xhr 库。

            var locationProtocol = location.protocol,
                locationHost = location.host;
            req.isXdUrl = function(url){
                if(/^\./.test(url)){
                    // begins with a dot is always relative to page URL; therefore not xdomain
                    return false;
                }
                if(/^\/\//.test(url)){
                    // for v1.6- backcompat, url starting with // indicates xdomain
                    return true;
                }
                // get protocol and host
                // \/+ takes care of the typical file protocol that looks like file:///drive/path/to/file
                // locationHost is falsy if file protocol => if locationProtocol matches and is "file:", || will return false
                var match = url.match(/^([^\/\:]+\:)\/+([^\/]+)/);
                return match && (match[1] != locationProtocol || (locationHost && match[2] != locationHost));
            };


            // note: to get the file:// protocol to work in FF, you must set security.fileuri.strict_origin_policy to false in about:config
            has.add("dojo-xhr-factory", 1);
            has.add("dojo-force-activex-xhr", has("host-browser") && !doc.addEventListener && window.location.protocol == "file:");
            has.add("native-xhr", typeof XMLHttpRequest != "undefined");
            if(has("native-xhr") && !has("dojo-force-activex-xhr")){
                getXhr = function(){
                    return new XMLHttpRequest();
                };
            }else{
                // if in the browser an old IE; find an xhr
                for(var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], progid, i = 0; i < 3;){
                    try{
                        progid = XMLHTTP_PROGIDS[i++];
                        if(new ActiveXObject(progid)){
                            // this progid works; therefore, use it from now on
                            break;
                        }
                    }catch(e){
                        // squelch; we're just trying to find a good ActiveX progid
                        // if they all fail, then progid ends up as the last attempt and that will signal the error
                        // the first time the client actually tries to exec an xhr
                    }
                }
                getXhr = function(){
                    return new ActiveXObject(progid);
                };
            }
            req.getXhr = getXhr;

            has.add("dojo-gettext-api", 1);
            req.getText = function(url, async, onLoad){
                var xhr = getXhr();
                xhr.open('GET', fixupUrl(url), false);
                xhr.send(null);
                if(xhr.status == 200 || (!location.host && !xhr.status)){
                    if(onLoad){
                        onLoad(xhr.responseText, async);
                    }
                }else{
                    throw makeError("xhrFailed", xhr.status);
                }
                return xhr.responseText;
            };
        }  // 结束对同步或者跨域加载的数据或才方法定义
    }else{
        req.async = 1;
    }


eval 方法

定义的eval 方法主要有两个用途,一是执行 script 侦测到的的 data-dojo-config 或 djConfig 中的数据 如 data-dojo-config="async:1",   即字符串转化为一个对像,并赋值给你一个对像。 二 是用于同步模块加载过来的代码,因为 Ajax加载过来的代码返回的都是文本,需要调用eval方法来执行相应的模块代码。
  var eval_ = new Function('return eval(argument[0]);'); // 使用构造函数, 这样我们的 eval_ 函数就能形成一个闭包,不会污染全局作用域

    req.eval = function(text,hint){
        return eval_(text + "\r\n////@ sourceURL=" + hint); 
    }


loader 内部事件API


内部事件API 主要用于报告错误, 配置发生改变,调试跟踪以及反馈当前的空前状态, 详细的使用可以参考 loader 文档
事件API需要实现以下功能:
  1. 需要有一个listenerQueues,用来保存所有事件类型及监听器, 格式如 {"error": [listen1, listen2, listen3]}
  2. on - 用于添加监听器,即把  on('error', handler) 添加到listenerQueues, 同时返回一个对像用于删除监听器。
  3. signal - 触发某个事件,从listenerQueres中调出想应的监听器。 
源代码及注释如下:
 // loader 事件 API

    var listenerQueues = {}, //监听器队列,用于保存所有的事件类型及对应的监听器
        error = "error",//因为错误类型的事件会在loader多次监听或者触发,所以定义一个error变量,可以直接在on(error, function(){}), 而不需要每次 on("error",function(){})
        on = req.on = function(type,listener){
            var queue = listenerQueues[type] || (listenerQueues[type]=[]); //如果没有存在相应的事件类型,则创建一个事件队列,并赋值。
            queue.push(listener);

            // 返回一个对像,用于删除监听器, 如  a=on('error',handler), 可以调用 a.remove() 删除listenerQueues中 error队列中的 handler 监听器。
            return{
                remove: function(){  //闭包函数,可以引用外围函数中的变量及参数
                    for(var i=0;i<queue.length;i++){
                        if(queue[i]==listener){
                            queue.splice(i,1);  //从队列中把监听器删除
                            return ;  //退出循环
                        }
                    }
                }
            }
        },
        signal = req.signal =function(type, args){
            var queue=listenerQueues[type];

            // 如果存在这个监听队列,则能过slice复制一个新的数组。这样可以保证,在这样触发中所有的监听器都会运行,不管其它的监听器是否有添加/删除其它监听器。 如 事件A, 有监听器 1,2,3, 1中会删除2,3中会添加4号监听器。复制的新数组还是1,2,3. 所有删除或新添加的监听器都会在第二次信号中触发
            forEach(queue&&queue.slice(0), function(listener){
                listener(null, isArray(args)?args:[args]); //处理函数采用单个形参, 所以确所传入的值为一个数组,易于处理
            })
        }

实现配置机制

为什么要实现配置机制呢? 通过配置机制,我们可以通过 defaultConfig, userConfig , 编译时配置,向加载器提供数据,规定相应的行为,如提供包的配置, 别名配置,然后加载器可以根据相应的配置来解析模块标识符到正确的路径。

配置选项

需要先定义部份的变量,来存放配置数据。详细如下,具体的描述可以参考 loader 文档
  • aliases -数组,  根据别名可以映射到真实的模块, aliases是一个数组,它的值是 [regexs or string, replacement] 形式的数组(alias, actual), alias可以是字符串,也可以是正则表达式。
  • paths -{}, 遵循CommonJs paths路径格式, 映射模块标识的部分,到paths中指定的值。 如{"a/b": "myApp/ core/widget"}, 当require(['a/b/alert']) 时,会调用 myApp/core/widget/alert.js
  • pathsMapProg - 数组, 将 paths中的值转化为一个路径的映身数组,易于检索和匹配路径,格式为:(from-path, to-path, regex, length),  length为"a/b"的长度, 结果如: [["a/b","myApp/ core/widget",RegExp /^a\/b(\/|$)/,3]], 详细的处理函数可以查看 computeMapProg
  • packs - 从packageId 到 包配置(packages) 的一个对像, 看可查看处理函数 fixupPackageInfo 了解底层处理.
  • map - {}, AMD Map配置变量,因为dojo/_base/kernel 需要引用这个对像,所以通过 req.map 导出这个对像。map:{ myOldApp:{dojo:"dojo16"}}
  • mapProgs - 如同对paths的处理, mapProgs 是 computeMapProg运行map后得到的一个数组。举上面那个map值, map:{myOldApp:{dojo:"dojo16"}, 这里有定义三个包 myOldApp, dojo, dojo16。 map的作用是将myOldApp中的 dojo 包标识,映像到dojo16,而不是dojo. mapProgs的值是一个数组,而不是对像,在检测匹配时会更加高效。 可以查看computeMapProg. mapProgs的值可以如下:[["myOldApp",[["dijit","dijit16",RegExp /^dijit(\/|$)/,5],["dojox","dojox16",RegExp /^dojox(\/|$)/,5],["dojo","dojo16",RegExp /^dojo(\/|$)/,4]],RegExp /^myOldApp(\/|$)/,8]]
  • modules - {}, 值为 (mid) -> module object. 模块对像的命名如下所示:
    • pid:  模块所属包对像的标识符。(如" dojo");  "" 标识系统或者默认的包
    • mid: 全面解析后的模块标识符(如已经使用了包映像和路径映身,mapProgs, pathsMapProg) , 但还没有加上包标识符(如:"dojo/io"script")
    • url: 从哪检索模块的路径
    • pack:  详细的包信息,这里是一个包对像,如Object { main="main"name="dojo"location="lib/dojo"}
    • executed: 0 表示没有执行 executing => 正在遍历依赖和运行模块的工厂函数; executed => 工厂函数运行完成
    • deps: 模块的依赖向量(向量可以理解为javascript 的数组,但是有先后顺序)
    • def : 模块的工厂函数
    • result: 运行完工厂函数返回的结果值
    • injected: (0 | requested | arrived ) , nonmodule 表示资源没有调用 define函数
    • load: 插件加载函数;仅能用于插件
    • modules 需要通过以下步骤才能创建
      1. Requested: 其它模块定义时或require 在依赖向量里包含了请求的模块,或者在运行代码里有明确通过 req.require 请求这个模块。
      2. Injected:  被请求资源的URL 构成一个 script元素,当这个script被添加到 插入点元素时。
      3. Loaded:  第【2】步注入的资源被加载完。
      4. Defined:  资源包含 define语句时,则告诉loader 这是一个模块。 注意 有的资源只包含 一堆代码 ,而不是通过define函数正规的定义一个模块。
      5. Evaluated:  模块通过define函数定义完成。 loader  加载器已经运行完工厂函数, 并返回结果。
  • cacheBust: "",  添加到模块URL后的查询字符串,破坏浏览器的缓存
  • cache: {}, 散列对像: (mid | url) --> (function | string), 资源的缓存列表。 可使用 mid-->function 或者 url-->string 从config.cache中获得一个资源。 url键跟mid键的区别就是包含 "url:"前缀.  url键提供的代表资源的url字符串。 mid键提供的是函数值。  (没有实际的例子举例,大家可以先忽略不管)
  • urlKeyPrefix = "url:",  在cache中添加到url键前面的字符串
  • pendingCacheInsert: hash:(mid)-->(function), 给定插入到cache中的缓存模块集合。 当缓存模块已经被发布到loader,  它们就会进入到 pendingCacheInsert;  然后根据AMD  define(1)或者 接受其它独立缓存模块,压入 cache (没明白), 第(1) 是常见的情况。  (没有实际的例子举例,大家可以先忽略不管)
  • dojoSniffConfig: {}, 一个关于配置变量的映射, 保存从 data-dojo-config中侦测到的配置数据
  • inertPointSibling =0 ,  节点用于定位角本(请求资源的角本)被插入到文档的哪个位置

配置核心

配置主要用来初始化一些之后会用到的包的信息,别名,map, paths, 以及缓存等。 实际开发中,开发者会在编译Dojo指定这些配置。 
配置有两种方法。一种是在指定 dojo-config-api时, loader会根据开发者指定的map, paths来自动计算 mapProgs, pathsMapProg. 
如果 dojo-config-api为false, 则需要手动defaultConfig指定以下选项, 这种方法很少使用。这里只是告诉你,dojo的配置机制,会对哪些方面进行配置。
// no config API, assume defaultConfig has everything the loader needs...for the entire lifetime of the application
		paths = defaultConfig.paths;
		pathsMapProg = defaultConfig.pathsMapProg;
		packs = defaultConfig.packs;
		aliases = defaultConfig.aliases;
		mapProgs = defaultConfig.mapProgs;
		modules = defaultConfig.modules;
		cache = defaultConfig.cache;
		cacheBust = defaultConfig.cacheBust;

		// remember the default config for other processes (e.g., dojo/config)
		req.rawConfig = defaultConfig;

如果dojo-config-api 为true. 会分以下三步进行配置,配置的核心函数是conifg= function(config, booting, referenceModule){}.  首先对defaultConfig 进行配置,之后是 userConfig , 最后是对侦测到的 sniffConfig.
配置的代码流程如下配置的代码流程如下:
if(has("dojo-config-api")){
var consumePendingCacheInsert = function(referenceModule){},
escapeString = function(s){
            
            },




			computeMapProg = function(map, dest){
				
			},




			computeAliases = function(config, dest){
				
			},








			fixupPackageInfo = function(packageInfo){
				
			},




			delayedModuleConfig
				
				= [],




			config = function(config, booting, referenceModule){




				
			};




	
       
		if(has("dojo-cdn") || has("dojo-sniff")){
			/*
               1. 如果在script中有指定dojo.js, 则获取 dojo.js的文件夹路径,做为baseUrl. 并获得data-dojo-config 或者 djConfig的数据。
               2. 记录script节点, 并赋值给变量insertPointSibling. 用于定位之后的角本注入位置。
               
            */
		}
        
		if(has("dojo-test-sniff")){
			/*
                在DOH单元测试时,将测试的配置添加到dojoSniffConfig对像中
            */
		}




		
		req.rawConfig = {};  //原生的配置
		config(defaultConfig, 1);  //进行默认配置




		
		if(has("dojo-cdn")){
			//如果dojo.js在CDN上,指定dojo, dijit, dojox等包的文件路径.
		}




		config(userConfig, 1); //进行用户配置








		config(dojoSniffConfig, 1);  //对侦测到的配置选项,进行处理




}



  1. userConfig 和 defaultConfig 都是通过初始化参数传递给Dojo. 而在<script>中的配置需要通过侦测技术完成。所以配置的第一步是获得<script>中的data-dojo-config 或 djConfig中的配置, 并根据src中指定的dojo.js路径,初始化basrUrl为 dojo.js文件夹路径。
以下是config 核心函数
config = function(config, booting, referenceModule){
				for(var p in config){
					if(p=="waitSeconds"){
						req.waitms = (config[p] || 0) * 1000;  //设置加载一个模块的超时时间, 单位为秒.
					}
					if(p=="cacheBust"){
						cacheBust = config[p] ? (isString(config[p]) ? config[p] : (new Date()).getTime() + "") : ""; //添加到URL后缀的字符串。以打断浏览器缓存。
					}
					if(p=="baseUrl" || p=="combo"){
						req[p] = config[p];  //设置baseUrl和 combo的值( dojo-combo-api是启动一些老版本的API, 可以不用设置)
					}
					if(has("dojo-sync-loader") && p=="async"){
						// falsy or "sync" => legacy sync loader
						// "xd" => sync but loading xdomain tree and therefore loading asynchronously (not configurable, set automatically by the loader)
						// "legacyAsync" => permanently in "xd" by choice
						// "debugAtAllCosts" => trying to load everything via script injection (not implemented)
						// otherwise, must be truthy => AMD
						// legacyMode: sync | legacyAsync | xd | false




                        //配置加载器的工作模式,AMD, 同步,或者跨域异步
						var mode = config[p];
						req.legacyMode = legacyMode = (isString(mode) && /sync|legacyAsync/.test(mode) ? mode : (!mode ? sync : false));
						req.async = !legacyMode;
					}




					if(config[p]!==hasCache){
                        //将defautlConfig, dojoConfig, dojoSniffConfig中的配置数据存入rawConfig.
                        // 将原生配置存入hasCache
						// accumulate raw config info for client apps which can use this to pass their own config
						req.rawConfig[p] = config[p];
						p!="has" && has.add("config-"+p, config[p], 0, booting);




					}
				}








				// make sure baseUrl exists  确保存在 baseUrl




				if(!req.baseUrl){
					req.baseUrl = "./";
				}
				// make sure baseUrl ends with a slash  确保baseUrl是以"/"结尾,即表示一个文件夹路径
				if(!/\/$/.test(req.baseUrl)){
					req.baseUrl += "/";
				}




				// now do the special work for has, packages, packagePaths, paths, aliases, and cache
                // 开始具体的配置工作,如has, packages, packagePaths, paths, aliases, mapProgs, pathsMapProg.
                // 如果在userConfig,defaultConfig中指定了has测试, 则把相应的has测试添加到hasCache中。
				for(p in config.has){
                    /*
                    var dojoConfig={
                         has: {
                         "dojo-firebug": true
                         }
                     }
                     */
					has.add(p, config.has[p], 0, booting);
				}
                //如果指定了packages, 对每个包进行处理,使每个包有固定的格式{name:**, location:**, main:**}, 如果有packageMap属性,则把值添加到map配置里. 详细请查看fixupPackageInfo.




				forEach(config.packages, fixupPackageInfo);




				// packagePath已经被弃用,会在2.0移除.
                /*
                var dojoConfig = {
                     packagePaths:{
                     "path/to/some/place":[
                     "myPackage",
                     {
                     name:"yourPackage",
                     main:"base"
                     }
                     ]
                     }
                 }
                 相当于
                     packages:[{
                     name:"myPackage",
                     location:"path/to/some/place/myPackage"
                     },{
                     name:"yourPackage",
                     location:"path/to/some/place/youPackage"
                     }]
                 */




				for(baseUrl in config.packagePaths){
					forEach(config.packagePaths[baseUrl], function(packageInfo){
						var location = baseUrl + "/" + packageInfo;
                        //如果只指定了一个字符串,如说明中的"myPackage"
						if(isString(packageInfo)){
							packageInfo = {name:packageInfo};
						}
						packageInfo.location = location;
						fixupPackageInfo(packageInfo);
					});
				}




				// notice that computeMapProg treats the dest as a reference; therefore, if/when that variable
				// is published (see dojo-publish-privates), the published variable will always hold a valid value.




				// this must come after all package processing since package processing may mutate map




                /*
                    注意computeMapProg 会将结果直接保存到dest, 如果变量被发布(可以通过require直接获得), 那么发布的变量会直接获得这个有效值
                    这个步骤必须在所有的package处理完成后才能进行,因为在包的处理过程中,会改变map的值。可以看上面的 packageMap.




                 map: {
                 myOldApp: {
                 dojo: "dojo16",
                 dijit: "dijit16",
                 dojox: "dojox16"
                 }
                 }
                 [["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]
                 */
				computeMapProg(mix(map, config.map), mapProgs);








                /*
                  对上面的 may-key 进一步处理,即 Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"} 也转化为一个四维向量数组




                    [["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]




                 */
				forEach(mapProgs, function(item){
					item[1] = computeMapProg(item[1], []);
					if(item[0]=="*"){
						mapProgs.star = item;
					}
				});




                /*
                    计算路径映射
                     paths:{
                     "a/b": "myApp/ core/widget"
                     }
                     [["a/b", "myApp/ core/widget", RegExp /^a\/b(\/|$)/, 3]]
                 */
				computeMapProg(mix(paths, config.paths), pathsMapProg);




				// aliases
                /*
                     aliases:[
                        ["text", "dojo/text"]
                     ]
                     [[RegExp /^text$/, "dojo/text"]]




                     1. 如果是字符串,别名需要跟目标字符串完全匹配
                     2. 别名可以采用正则表达式的方式.
                 */
				computeAliases(config.aliases, aliases);




                //给某个模块传递配置。如果指定了booting为1,刚在loader启动时,不进行模块配置。
                // 在实际开发中,好像都不会对模块进行配置。可以忽略delayedModuleConfig 和在 dojoConfig={ config:{模块名称: 配置}}
				if(booting){




					delayedModuleConfig.push({config:config.config});
				}else{
					for(p in config.config){




						var module = getModule(p, referenceModule);
						module.config = mix(module.config || {}, config.config[p]);
					}
				}




				// push in any new cache values
				if(config.cache){
                    /*
                       不能理解 cache, pendingCaccheInsert, consumePendingCacheInsert.
                       没有找到相关的使用例子.




                     */
					consumePendingCacheInsert();
					pendingCacheInsert = config.cache;
					if(config.cache["*noref"]){
						consumePendingCacheInsert();
					}
				}




				signal("config", [config, req.rawConfig]);  //触发本次配置完成的事件, 在trace中有用
			};



对于delayedModuelConfig,cache, pending, consumePendingCacheInsert, 大家只需要知道就可以,在实际中没有发现相关的使用。所以可以忽略这些内容。

加载器核心

核心包括contextRequire及模块名解析,直接上源代码注释,大家有时间自己看。
(function(
	userConfig,
	defaultConfig
){
    /*
	 summary: 概述
			This is the "source loader" and is the entry point for Dojo during development. You may also load Dojo with
			any AMD-compliant loader via the package main module dojo/main.
            这个是一个源文件加载器,在dojo的开发过程中,它是整个应用程序的入口。 你也可以通过其它遵循AMD规范的加载器来加载Dojo, 如requirejs. 如果你采用第三方的加截器,可以不用使用本文件,而是直接加载 dojo/main
            模块
	 description: 描述
			This is the "source loader" for Dojo. It provides an AMD-compliant loader that can be configured
			to operate in either synchronous or asynchronous modes. After the loader is defined, dojo is loaded
			IAW the package main module dojo/main. In the event you wish to use a foreign loader, you may load dojo as a package
			via the package main module dojo/main and this loader is not required; see dojo/package.json for details.
            这是Dojo的一个源文件加载器,它遵循 AMD规范,可以配置成同步或异步模式。当加载器被定义好后(即用于浏览器,nodejs环境), dojo的主体会被加载进来,这主要是依据 dojo/main模块。如果想使用一个外部的
            加载器(如requirejs), 你可以直接将 dojo/main进行加载,而这个loader不必加载;需要了解更多,请查看 dojo/package.json;

			In order to keep compatibility with the v1.x line, this loader includes additional machinery that enables
			the dojo.provide, dojo.require et al API. This machinery is loaded by default, but may be dynamically removed
			via the has.js API and statically removed via the build system.
            考虑到向前兼容dojo的版本, 这个加载器包含一些额外机制,如使 dojo.provide, dojo.requre; 这些机制默认情况下是包含的,但可以通过 has.js 的API动态删除或通过 dojo的build system 手动删除。

			This loader includes sniffing machinery to determine the environment; the following environments are supported:
            这个加载器也包含嗅探机制去发现当前使用的环境, 以下三种会被支持。

			- browser
			- node.js
			- rhino  Java 编写的一个解释器,由 Mozilla 开发

			This is the so-called "source loader". As such, it includes many optional features that may be discarded by
			building a customized version with the build system.
            这就是所谓的源文件加载器,如上陈述, 它可能通过 build system可以去除掉一些功能,而形成你自己想要的版本。.
	 Design and Implementation Notes 设计和实现日记

	 This function defines an AMD-compliant (http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition)
	 loader that can be configured to operate in either synchronous or asynchronous modes.
     这个功能定议一个遵循AMD规范(http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition)的加载器。 可以配置成同步或异步加载模式。

     ! 有空了解下AMD规范

     here is a road map of the contents:
     以下是这个内容的路线图。
		 1. Small library for use implementing the loader.  加载器会使用到的一个很小的库,即工具函数。
		 2. Define the has.js API; this is used throughout the loader to bracket features.定义一个has.js API; 整个加载器都会使用到它来特征检测
		 3. Define the node.js and rhino sniffs and sniff. 定义侦测node.js 及rhino 的嗅探器
	     4. Define the loader's data. 定义一个loader的数据模型
		 5. Define the configuration machinery. 实现配置机制
		 6. Define the script element sniffing machinery and sniff for configuration data. 定义一个角本元素嗅探机制,并且将嗅探的结果作为配置数据
		 7. Configure the loader IAW the provided user, default, and sniffing data. 依据提供的用户,默认参数及嗅探到的信息,配置一个加载器
		 8. Define the global require function. 定义一个全局加载函数
		 9. Define the module resolution machinery 定义一个模块解析机制.
		10. Define the module and plugin module definition machinery 定义一个模块及插件开发规范
		11. Define the script injection machinery. 定义一个角本注入机制
		12. Define the window load detection. 定义一个window 加载侦测
		13. Define the logging API. 定义一个日志API
		14. Define the tracing API. 定义一个跟踪API, 即调试用的API
		15. Define the AMD define function. 实现AMD规范中需要定义的函数
		16. Define the dojo v1.x provide/require machinery--so called "legacy" modes. 为了兼容之前老的dojo, 定义provide/ require。
		17. Publish global variables. 发布一些全局变量

	 Language and Acronyms and Idioms
     束语
	 moduleId: a CJS(CommonJS) module identifier, (used for public APIs   对外的CommonJS 模块标识符
	 mid: moduleId (used internally)   对内的模块ID(标识符)
	 packageId: a package identifier (used for public APIs) 对外的包标识符
	 pid: packageId (used internally); the implied system or default package has pid==="" 对内的包id, 如是是默认的包,则pid为空字符
	 pack: package is used internally to reference a package object (since javascript has reserved words including "package")  包对象
	 prid: plugin resource identifier 插件标识符
	 referenceModule: 参照模块,用代码来表示更好理解, require['dojo/dom'], 而 dom里面依赖 define(["./sniff", "./_base/window"], sniff, window. 在加载sniff时, referenceModule代表的就是dom 模块.

	 The integer constant 1 is used in place of true and 0 in place of false   常量1及0 ,分别用作ture及false的占位符

	*/


	var	noop = function(){
		},

		isEmpty = function(it){
			for(var p in it){
				return 0;
			}
			return 1;
		},

		toString = {}.toString,

		isFunction = function(it){
			return toString.call(it) == "[object Function]";
		},

		isString = function(it){
			return toString.call(it) == "[object String]";
		},

		isArray = function(it){
			return toString.call(it) == "[object Array]";
		},

		forEach = function(vector, callback){
			if(vector){
				for(var i = 0; i < vector.length;){
					callback(vector[i++]);
				}
			}
		},

		mix = function(dest, src){
			for(var p in src){
				dest[p] = src[p];
			}
			return dest;
		},

		makeError = function(error, info){
			return mix(new Error(error), {src:"dojoLoader", info:info});
		},

		uidSeed = 1,

		uid = function(){
			// Returns a unique identifier (within the lifetime of the document) of the form /_d+/.  返回一个唯一标识符(文档的生命周期内),格式为 “_数字” 形式

			return "_" + uidSeed++;
		},

		// FIXME: how to doc window.require() api  FIXME 标识此注释的内容代码需要修正 TODO 标识此处功能需要实现, 这里说的是需要修正 require的api 文档

		// this will be the global require function;
        // 全局的 require 函数
		req = function(
			config,		  //(object, optional) hash of configuration properties  传入的第一个值为配置属性的散列对象(可选)
			dependencies, //(array of commonjs.moduleId, optional) list of modules to be loaded before applying callback 传入的第二个参数为,在调用回调函数之前,需要加载的模块id, 该模块id遵循commonjs模范(可选)
			callback	  //(function, optional) lambda expression to apply to module values implied by dependencies,传入一个匿名函数,当依赖的模块加载完后,会调用这个函数
		){
			return contextRequire(config, dependencies, callback, 0, req);
		},

    // the loader uses the has.js API to control feature inclusion/exclusion; define then use throughout  loader 通过has.js API来包含或者排除某些特征
		global = this,

		doc = global.document,

		element = doc && doc.createElement("DiV"),

		has = req.has = function(name){
			return isFunction(hasCache[name]) ? (hasCache[name] = hasCache[name](global, doc, element)) : hasCache[name];
		},

		hasCache = has.cache = defaultConfig.hasCache;

	has.add = function(name, test, now, force){ // 添加某个测试。
		(hasCache[name]===undefined || force) && (hasCache[name] = test); // 测试缓存中还没有测试结果,或者要强制覆盖原有的测试, hasCache[name]=test;
		return now && has(name);  //指定第三个参数为true时,立即返回检测的结果。
	};

    /*
        以下测试是否为 nodejs环境,如果是,加载./_base/configNode.js的文件。
        如果用户的配置中有指定has对象,并且存在"host-node", 那么直接返回用户指定的值。 惹没有指定,则测试是否有全局对象process, 并测试它的版本是否为node.
        例:
        nodejs环境
         hasCache['host-node'] = 1
        在这里,没有设定has.add的第三个参数。所以只是设置了hasCache['host-node']的值,而不返回,在if中,会通过has("host-node")来再获得相应的值。
    */
	has.add("host-node", userConfig.has && "host-node" in userConfig.has ?
		userConfig.has["host-node"] :
		(typeof process == "object" && process.versions && process.versions.node && process.versions.v8));
	if(has("host-node")){
		// fixup the default config for node.js environment
		require("./_base/configNode.js").config(defaultConfig);
		// remember node's require (with respect to baseUrl==dojo's root)
		defaultConfig.loaderPatch.nodeRequire = require;
	}
    /*
     用于检测rhino环境
     */
	has.add("host-rhino", userConfig.has && "host-rhino" in userConfig.has ?
		userConfig.has["host-rhino"] :
		(typeof load == "function" && (typeof Packages == "function" || typeof Packages == "object")));
	if(has("host-rhino")){
		// owing to rhino's lame feature that hides the source of the script, give the user a way to specify the baseUrl...
		for(var baseUrl = userConfig.baseUrl || ".", arg, rhinoArgs = this.arguments, i = 0; i < rhinoArgs.length;){
			arg = (rhinoArgs[i++] + "").split("=");
			if(arg[0] == "baseUrl"){
				baseUrl = arg[1];
				break;
			}
		}
		load(baseUrl + "/_base/configRhino.js");
		rhinoDojoConfig(defaultConfig, baseUrl, rhinoArgs);
	}

	// userConfig has tests override defaultConfig has tests; do this after the environment detection because
	// the environment detection usually sets some has feature values in the hasCache.
    // 用户配置中的has测试会覆盖 defaultConfig 中的 has测试。 但这会发现在环境侦测之后, 因为环境侦没通常会在hasCache里设置特征检测的值。
    // 总体的意思就是在确定loader使用环境后,用 userConfig中的has 替换defaultConfig指定的has测试
	for(var p in userConfig.has){
		has.add(p, userConfig.has[p], 0, 1);
	}

	//
	// define the loader data  定义加载器的数据
	//

	// the loader will use these like symbols if the loader has the traceApi; otherwise
	// define magic numbers so that modules can be provided as part of defaultConfig
    /*
    如果有指定traceApi(跟踪功能), 定义的变量相当于标识的符号,表示模块是在请求,到达,正在执行,执行完成等各种状态。否则,定义的数字,模块可以作为defaultConfig的一部分。
    以下变量指定模块当前的状态
     */
	var	requested = 1,
		arrived = 2,
		nonmodule = 3,
		executing = 4,
		executed = 5;

	if(has("dojo-trace-api")){

		// these make debugging nice;  don't do it for production code 使调试变得容易,不要将他用于产品代码。
		requested = "requested";
		arrived = "arrived";
		nonmodule = "not-a-module";
		executing = "executing";
		executed = "executed";
	}

	var legacyMode = 0,
		sync = "sync",
		xd = "xd",
		syncExecStack = [],
		dojoRequirePlugin = 0,
		checkDojoRequirePlugin = noop,
		transformToAmd = noop,
		getXhr;
    // 是否可以使用1.7之前的加载器
	if(has("dojo-sync-loader")){
		req.isXdUrl = noop;

		req.initSyncLoader = function(dojoRequirePlugin_, checkDojoRequirePlugin_, transformToAmd_){
			// the first dojo/_base/loader loaded gets to define these variables; they are designed to work
			// in the presence of zero to many mapped dojo/_base/loaders
			if(!dojoRequirePlugin){
				dojoRequirePlugin = dojoRequirePlugin_;
				checkDojoRequirePlugin = checkDojoRequirePlugin_;
				transformToAmd = transformToAmd_;
			}

			return {
				sync:sync,
				requested:requested,
				arrived:arrived,
				nonmodule:nonmodule,
				executing:executing,
				executed:executed,
				syncExecStack:syncExecStack,
				modules:modules,
				execQ:execQ,
				getModule:getModule,
				injectModule:injectModule,
				setArrived:setArrived,
				signal:signal,
				finishExec:finishExec,
				execModule:execModule,
				dojoRequirePlugin:dojoRequirePlugin,
				getLegacyMode:function(){return legacyMode;},
				guardCheckComplete:guardCheckComplete
			};
		};

		if(has("dom")){
			// in legacy sync mode, the loader needs a minimal XHR library

			var locationProtocol = location.protocol,
				locationHost = location.host;
			req.isXdUrl = function(url){
				if(/^\./.test(url)){
					// begins with a dot is always relative to page URL; therefore not xdomain
					return false;
				}
				if(/^\/\//.test(url)){
					// for v1.6- backcompat, url starting with // indicates xdomain
					return true;
				}
				// get protocol and host
				// \/+ takes care of the typical file protocol that looks like file:///drive/path/to/file
				// locationHost is falsy if file protocol => if locationProtocol matches and is "file:", || will return false
				var match = url.match(/^([^\/\:]+\:)\/+([^\/]+)/);
				return match && (match[1] != locationProtocol || (locationHost && match[2] != locationHost));
			};


			// note: to get the file:// protocol to work in FF, you must set security.fileuri.strict_origin_policy to false in about:config
			has.add("dojo-xhr-factory", 1);
			has.add("dojo-force-activex-xhr", has("host-browser") && !doc.addEventListener && window.location.protocol == "file:");
			has.add("native-xhr", typeof XMLHttpRequest != "undefined");
			if(has("native-xhr") && !has("dojo-force-activex-xhr")){
				getXhr = function(){
					return new XMLHttpRequest();
				};
			}else{
				// if in the browser an old IE; find an xhr
				for(var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], progid, i = 0; i < 3;){
					try{
						progid = XMLHTTP_PROGIDS[i++];
						if(new ActiveXObject(progid)){
							// this progid works; therefore, use it from now on
							break;
						}
					}catch(e){
						// squelch; we're just trying to find a good ActiveX progid
						// if they all fail, then progid ends up as the last attempt and that will signal the error
						// the first time the client actually tries to exec an xhr
					}
				}
				getXhr = function(){
					return new ActiveXObject(progid);
				};
			}
			req.getXhr = getXhr;

			has.add("dojo-gettext-api", 1);
			req.getText = function(url, async, onLoad){
				var xhr = getXhr();
				xhr.open('GET', fixupUrl(url), false);
				xhr.send(null);
				if(xhr.status == 200 || (!location.host && !xhr.status)){
					if(onLoad){
						onLoad(xhr.responseText, async);
					}
				}else{
					throw makeError("xhrFailed", xhr.status);
				}
				return xhr.responseText;
			};
		}
	}else{
		req.async = 1;
	}

	//
	// loader eval
	//
	var eval_ =
		// use the function constructor so our eval is scoped close to (but not in) in the global space with minimal pollution 使用函数的构造函数,便我们定义的eval形成闭包,不会污染全局作域用。
		new Function('return eval(arguments[0]);');

	req.eval =
		function(text, hint){

			return eval_(text + "\r\n////@ sourceURL=" + hint);
		};

	//
	// loader micro events API
	// loader 内部事件系统



	var listenerQueues = {},
		error = "error",
		signal = req.signal = function(type, args){
			var queue = listenerQueues[type];
			// notice we run a copy of the queue; this allows listeners to add/remove
			// other listeners without affecting this particular signal
			forEach(queue && queue.slice(0), function(listener){
				listener.apply(null, isArray(args) ? args : [args]);
			});
		},
		on = req.on = function(type, listener){

			// notice a queue is not created until a client actually connects
			var queue = listenerQueues[type] || (listenerQueues[type] = []);
			queue.push(listener);
			return {
				remove:function(){
					for(var i = 0; i<queue.length; i++){
						if(queue[i]===listener){
							queue.splice(i, 1);
							return;
						}
					}
				}
			};
		};


	// configuration machinery; with an optimized/built defaultConfig, all configuration machinery can be discarded
	// lexical variables hold key loader data structures to help with minification; these may be completely,
	// one-time initialized by defaultConfig for optimized/built versions
    // 实现loader的配置机制;

	var
		aliases
			// a vector of pairs of [regexs or string, replacement] => (alias, actual)
			= [],

		paths
			// CommonJS paths
			= {},

		pathsMapProg
			// list of (from-path, to-path, regex, length) derived from paths;
			// a "program" to apply paths; see computeMapProg
			= [],

		packs
			// a map from packageId to package configuration object; see fixupPackageInfo
			= {},

		map = req.map
			// AMD map config variable; dojo/_base/kernel needs req.map to figure out the scope map
			= {},

		mapProgs
			// vector of quads as described by computeMapProg; map-key is AMD map key, map-value is AMD map value
			= [],

		modules
			// A hash:(mid) --> (module-object) the module namespace
			//
			// pid: the package identifier to which the module belongs (e.g., "dojo"); "" indicates the system or default package
			// mid: the fully-resolved (i.e., mappings have been applied) module identifier without the package identifier (e.g., "dojo/io/script")
			// url: the URL from which the module was retrieved
			// pack: the package object of the package to which the module belongs
			// executed: 0 => not executed; executing => in the process of traversing deps and running factory; executed => factory has been executed
			// deps: the dependency vector for this module (vector of modules objects)
			// def: the factory for this module
			// result: the result of the running the factory for this module
			// injected: (0 | requested | arrived) the status of the module; nonmodule means the resource did not call define
			// load: plugin load function; applicable only for plugins
			//
			// Modules go through several phases in creation:
			//
			// 1. Requested: some other module's definition or a require application contained the requested module in
			//	  its dependency vector or executing code explicitly demands a module via req.require.
			//
			// 2. Injected: a script element has been appended to the insert-point element demanding the resource implied by the URL
			//
			// 3. Loaded: the resource injected in [2] has been evaluated.
			//
			// 4. Defined: the resource contained a define statement that advised the loader about the module. Notice that some
			//	  resources may just contain a bundle of code and never formally define a module via define
			//
			// 5. Evaluated: the module was defined via define and the loader has evaluated the factory and computed a result.
			= {},

		cacheBust
			// query string to append to module URLs to bust browser cache
			= "",

		cache
			// hash:(mid | url)-->(function | string)
			//
			// A cache of resources. The resources arrive via a config.cache object, which is a hash from either mid --> function or
			// url --> string. The url key is distinguished from the mid key by always containing the prefix "url:". url keys as provided
			// by config.cache always have a string value that represents the contents of the resource at the given url. mid keys as provided
			// by config.cache always have a function value that causes the same code to execute as if the module was script injected.
			//
			// Both kinds of key-value pairs are entered into cache via the function consumePendingCache, which may relocate keys as given
			// by any mappings *iff* the config.cache was received as part of a module resource request.
			//
			// Further, for mid keys, the implied url is computed and the value is entered into that key as well. This allows mapped modules
			// to retrieve cached items that may have arrived consequent to another namespace.
			//
			 = {},

		urlKeyPrefix
			// the prefix to prepend to a URL key in the cache.
			= "url:",

		pendingCacheInsert
			// hash:(mid)-->(function)
			//
			// Gives a set of cache modules pending entry into cache. When cached modules are published to the loader, they are
			// entered into pendingCacheInsert; modules are then pressed into cache upon (1) AMD define or (2) upon receiving another
			// independent set of cached modules. (1) is the usual case, and this case allows normalizing mids given in the pending
			// cache for the local configuration, possibly relocating modules.
			 = {},

		dojoSniffConfig
			// map of configuration variables
			// give the data-dojo-config as sniffed from the document (if any)
			= {},

		insertPointSibling
			// the nodes used to locate where scripts are injected into the document
			= 0;
    // 确保在 dojo在 build时可配置。提供配置API,这样可以将map或者paths中的值处理为 mapProgs, pathsMapProgs, 主要是在编译时,这些配置信息都是存放在profile 文件里,可以查看profile文件的格式。 Ensures that the build is configurable
	if(has("dojo-config-api")){
		var consumePendingCacheInsert = function(referenceModule){

				var p, item, match, now, m;

				for(p in pendingCacheInsert){

					item = pendingCacheInsert[p];
					match = p.match(/^url\:(.+)/);
					if(match){
						cache[urlKeyPrefix + toUrl(match[1], referenceModule)] =  item;
					}else if(p=="*now"){
						now = item;
					}else if(p!="*noref"){
						m = getModuleInfo(p, referenceModule, true);
						cache[m.mid] = cache[urlKeyPrefix + m.url] = item;
					}
				}
				if(now){
					now(createRequire(referenceModule));
				}
				pendingCacheInsert = {};
			},

			escapeString = function(s){
				return s.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(c){ return "\\" + c; });
			},

			computeMapProg = function(map, dest){
				// This routine takes a map as represented by a JavaScript object and initializes dest, a vector of
				// quads of (map-key, map-value, refex-for-map-key, length-of-map-key), sorted decreasing by length-
				// of-map-key. The regex looks for the map-key followed by either "/" or end-of-string at the beginning
				// of a the search source. Notice the map-value is irrelevant to the algorithm
                /*
                    这段程序获得一个由Javascript 对像表示的map配置数据, 然后会初始化dest(存放结果的数组),dest中存放的值都是四维向量(map-key, map-value, map-key的正则表达式, map-key的长度)
                    程序会对dest中的值,根据map-key的长度进行降序排序. 正则表达式用于搜索目标对像的开头是否是may-key + "/" 或者may-key + "$" (完全匹配). 注意,map-value跟算法没有什么关系。
                 */
                /*
                 map: {
                     myOldApp: {
                     dojo: "dojo16",
                     dijit: "dijit16",
                     dojox: "dojox16"
                     }
                 }
                 [["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]


                 paths:{
                 "a/b": "myApp/ core/widget"
                 }

                 [["a/b", "myApp/ core/widget", RegExp /^a\/b(\/|$)/, 3]]
                 */

				dest.splice(0, dest.length);

				for(var p in map){

					dest.push([
						p,
						map[p],
						new RegExp("^" + escapeString(p) + "(\/|$)"),
						p.length]);

				}
				dest.sort(function(lhs, rhs){ return rhs[3] - lhs[3]; });
				return dest;
			},

			computeAliases = function(config, dest){
                /*
                    将键值对,转化为正则表达式形式的键值对
                 aliases:[
                 ["text", "dojo/text"]
                 ]
                 [[RegExp /^text$/, "dojo/text"]]
                 */
				forEach(config, function(pair){
					// take a fixed-up copy...
                    //如果是字符串,则构造一个正则表达式,如果是函数或者正则表达式,直接使用键。
					dest.push([isString(pair[0]) ? new RegExp("^" + escapeString(pair[0]) + "$") : pair[0], pair[1]]);
				});
			},


			fixupPackageInfo = function(packageInfo){
				// calculate the precise (name, location, main, mappings) for a package 计算每个包的准确信息,包的信息包括,名字,位置, 主文件名称, 映射情况。
                /*
                 packages: [
                 {location:"lib/dojo"},  //  标注1
                 { name: "dojo16", location: "lib/dojo16" },
                 { name: "dijit16", location: "lib/dijit16" },
                 { name: "dojox16", location: "lib/dojox16" },
                 { name: "dojo", location: "lib/dojo" },
                 { name: "dijit", location: "lib/dijit" },
                 { name: "dojox", location: "lib/dojox" },
                 {name:"doh", location:"lib/util/doh"},
                 { name: "myOldApp", location: "myOldApp", packageMap:{dojo:"dojo16"} },
                 { name: "my", location: "my" }
                 ]

                 */
				var name = packageInfo.name;
				if(!name){
					// packageInfo must be a string that gives the name
                    // 如果没有在包中指定name, 则整个包对像作为包的名称, 如标注1, name= {location:"lib/dojo"}
					name = packageInfo;
					packageInfo = {name:name};
				}
				packageInfo = mix({main:"main"}, packageInfo);  //mix(dest,src)

				packageInfo.location = packageInfo.location ? packageInfo.location : name;  //如果没有指定包的位置,刚location为包的名称。

				// packageMap is deprecated in favor of AMD map, packageMap已经被弃用,被map配置替代
				if(packageInfo.packageMap){
					map[name] = packageInfo.packageMap;  //将packageMap值作为 map对像中的一个值。
				}

				if(!packageInfo.main.indexOf("./")){
					packageInfo.main = packageInfo.main.substring(2);  //如果main以"./"开头,则去掉"./"
				}

				// now that we've got a fully-resolved package object, push it into the configuration
                // 现在我们获得一个完全解析后的一个包对像, 即原始的包对像可能只有名称和location,处理后的包对像,都有 name, location, main属性,有的还有packageMap,packageMap不是很重要,在这里只是将他的值添加到map里。
				packs[name] = packageInfo;

			},

			delayedModuleConfig
				// module config cannot be consumed until the loader is completely initialized; therefore, all
				// module config detected during booting is memorized and applied at the end of loader initialization
				// TODO: this is a bit of a kludge; all config should be moved to end of loader initialization, but
				// we'll delay this chore and do it with a final loader 1.x cleanup after the 2.x loader prototyping is complete
            = [],


			config = function(config, booting, referenceModule){
				for(var p in config){
					if(p=="waitSeconds"){
						req.waitms = (config[p] || 0) * 1000;  //设置加载一个模块的超时时间, 单位为秒.
					}
					if(p=="cacheBust"){
						cacheBust = config[p] ? (isString(config[p]) ? config[p] : (new Date()).getTime() + "") : ""; //添加到URL后缀的字符串。以打断浏览器缓存。
					}
					if(p=="baseUrl" || p=="combo"){
						req[p] = config[p];  //设置baseUrl和 combo的值( dojo-combo-api是启动一些老版本的API, 可以不用设置)
					}
					if(has("dojo-sync-loader") && p=="async"){
						// falsy or "sync" => legacy sync loader
						// "xd" => sync but loading xdomain tree and therefore loading asynchronously (not configurable, set automatically by the loader)
						// "legacyAsync" => permanently in "xd" by choice
						// "debugAtAllCosts" => trying to load everything via script injection (not implemented)
						// otherwise, must be truthy => AMD
						// legacyMode: sync | legacyAsync | xd | false

                        //配置加载器的工作模式,AMD, 同步,或者跨域异步
						var mode = config[p];
						req.legacyMode = legacyMode = (isString(mode) && /sync|legacyAsync/.test(mode) ? mode : (!mode ? sync : false));
						req.async = !legacyMode;
					}

					if(config[p]!==hasCache){
                        //将defautlConfig, dojoConfig, dojoSniffConfig中的配置数据存入rawConfig.
                        // 将原生配置存入hasCache
						// accumulate raw config info for client apps which can use this to pass their own config
						req.rawConfig[p] = config[p];
						p!="has" && has.add("config-"+p, config[p], 0, booting);

					}
				}


				// make sure baseUrl exists  确保存在 baseUrl

				if(!req.baseUrl){
					req.baseUrl = "./";
				}
				// make sure baseUrl ends with a slash  确保baseUrl是以"/"结尾,即表示一个文件夹路径
				if(!/\/$/.test(req.baseUrl)){
					req.baseUrl += "/";
				}

				// now do the special work for has, packages, packagePaths, paths, aliases, and cache
                // 开始具体的配置工作,如has, packages, packagePaths, paths, aliases, mapProgs, pathsMapProg.
                // 如果在userConfig,defaultConfig中指定了has测试, 则把相应的has测试添加到hasCache中。
				for(p in config.has){
                    /*
                    var dojoConfig={
                         has: {
                         "dojo-firebug": true
                         }
                     }
                     */
					has.add(p, config.has[p], 0, booting);
				}
                //如果指定了packages, 对每个包进行处理,使每个包有固定的格式{name:**, location:**, main:**}, 如果有packageMap属性,则把值添加到map配置里. 详细请查看fixupPackageInfo.

				forEach(config.packages, fixupPackageInfo);

				// packagePath已经被弃用,会在2.0移除.
                /*
                var dojoConfig = {
                     packagePaths:{
                     "path/to/some/place":[
                     "myPackage",
                     {
                     name:"yourPackage",
                     main:"base"
                     }
                     ]
                     }
                 }
                 相当于
                     packages:[{
                     name:"myPackage",
                     location:"path/to/some/place/myPackage"
                     },{
                     name:"yourPackage",
                     location:"path/to/some/place/youPackage"
                     }]
                 */

				for(baseUrl in config.packagePaths){
					forEach(config.packagePaths[baseUrl], function(packageInfo){
						var location = baseUrl + "/" + packageInfo;
                        //如果只指定了一个字符串,如说明中的"myPackage"
						if(isString(packageInfo)){
							packageInfo = {name:packageInfo};
						}
						packageInfo.location = location;
						fixupPackageInfo(packageInfo);
					});
				}

				// notice that computeMapProg treats the dest as a reference; therefore, if/when that variable
				// is published (see dojo-publish-privates), the published variable will always hold a valid value.

				// this must come after all package processing since package processing may mutate map

                /*
                    注意computeMapProg 会将结果直接保存到dest, 如果变量被发布(可以通过require直接获得), 那么发布的变量会直接获得这个有效值
                    这个步骤必须在所有的package处理完成后才能进行,因为在包的处理过程中,会改变map的值。可以看上面的 packageMap.

                 map: {
                 myOldApp: {
                 dojo: "dojo16",
                 dijit: "dijit16",
                 dojox: "dojox16"
                 }
                 }
                 [["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]
                 */
				computeMapProg(mix(map, config.map), mapProgs);


                /*
                  对上面的 may-key 进一步处理,即 Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"} 也转化为一个四维向量数组

                    [["myOldApp", Object { dojo="dojo16", dijit="dijit16", dojox="dojox16"}, RegExp /^myOldApp(\/|$)/, 8]]

                 */
				forEach(mapProgs, function(item){
					item[1] = computeMapProg(item[1], []);
					if(item[0]=="*"){
						mapProgs.star = item;
					}
				});

                /*
                    计算路径映射
                     paths:{
                     "a/b": "myApp/ core/widget"
                     }
                     [["a/b", "myApp/ core/widget", RegExp /^a\/b(\/|$)/, 3]]
                 */
				computeMapProg(mix(paths, config.paths), pathsMapProg);

				// aliases
                /*
                     aliases:[
                        ["text", "dojo/text"]
                     ]
                     [[RegExp /^text$/, "dojo/text"]]

                     1. 如果是字符串,别名需要跟目标字符串完全匹配
                     2. 别名可以采用正则表达式的方式.
                 */
				computeAliases(config.aliases, aliases);

                //给某个模块传递配置。如果指定了booting为1,刚在loader启动时,不进行模块配置。
                // 在实际开发中,好像都不会对模块进行配置。可以忽略delayedModuleConfig 和在 dojoConfig={ config:{模块名称: 配置}}
				if(booting){

					delayedModuleConfig.push({config:config.config});
				}else{
					for(p in config.config){

						var module = getModule(p, referenceModule);
						module.config = mix(module.config || {}, config.config[p]);
					}
				}

				// push in any new cache values
				if(config.cache){
                    /*
                       不能理解 cache, pedingCaccheInsert, consumePendingCacheInsert.
                       没有找到相关的使用例子.

                     */
					consumePendingCacheInsert();
					pendingCacheInsert = config.cache;
					if(config.cache["*noref"]){
						consumePendingCacheInsert();
					}
				}

				signal("config", [config, req.rawConfig]);
			};

		//
		// execute the various sniffs; userConfig can override and value
		//
        //  可以获得浏览器 user agent的信息
		if(has("dojo-cdn") || has("dojo-sniff")){
			// the sniff regex looks for a src attribute ending in dojo.js, optionally preceded with a path.
			// match[3] returns the path to dojo.js (if any) without the trailing slash. This is used for the
			// dojo location on CDN deployments and baseUrl when either/both of these are not provided
			// explicitly in the config data; this is the 1.6- behavior.

			var scripts = doc.getElementsByTagName("script"),
				i = 0,
				script, dojoDir, src, match;
			while(i < scripts.length){
				script = scripts[i++];
				if((src = script.getAttribute("src")) && (match = src.match(/(((.*)\/)|^)dojo\.js(\W|$)/i))){
					// sniff dojoDir and baseUrl
					dojoDir = match[3] || "";
					defaultConfig.baseUrl = defaultConfig.baseUrl || dojoDir;

					// remember an insertPointSibling
					insertPointSibling = script;
				}

				// sniff configuration on attribute in script element
				if((src = (script.getAttribute("data-dojo-config") || script.getAttribute("djConfig")))){

					dojoSniffConfig = req.eval("({ " + src + " })", "data-dojo-config");

					// remember an insertPointSibling
					insertPointSibling = script;
				}

				// sniff requirejs attribute
				if(has("dojo-requirejs-api")){
					if((src = script.getAttribute("data-main"))){
						dojoSniffConfig.deps = dojoSniffConfig.deps || [src];
					}
				}
			}
		}
        // 禁止将 doh(单元测试)里的配置, 混合到dojoSniffConfig ( dojo.js 中 data-dojo-config扫描到的配置)
		if(has("dojo-test-sniff")){
			// pass down doh.testConfig from parent as if it were a data-dojo-config
			try{
				if(window.parent != window && window.parent.require){
					var doh = window.parent.require("doh");

					doh && mix(dojoSniffConfig, doh.testConfig);
				}
			}catch(e){}
		}

		// configure the loader; let the user override defaults
		req.rawConfig = {};
		config(defaultConfig, 1);

		// do this before setting userConfig/sniffConfig to allow userConfig/sniff overrides
		if(has("dojo-cdn")){
			packs.dojo.location = dojoDir;
			if(dojoDir){
				dojoDir += "/";
			}
			packs.dijit.location = dojoDir + "../dijit/";
			packs.dojox.location = dojoDir + "../dojox/";
		}

		config(userConfig, 1);


		config(dojoSniffConfig, 1);

	}else{
		// no config API, assume defaultConfig has everything the loader needs...for the entire lifetime of the application
		paths = defaultConfig.paths;

		pathsMapProg = defaultConfig.pathsMapProg;
		packs = defaultConfig.packs;
		aliases = defaultConfig.aliases;
		mapProgs = defaultConfig.mapProgs;
		modules = defaultConfig.modules;
		cache = defaultConfig.cache;
		cacheBust = defaultConfig.cacheBust;

		// remember the default config for other processes (e.g., dojo/config)
		req.rawConfig = defaultConfig;
	}


	if(has("dojo-combo-api")){
        // 已被弃用
		req.combo = req.combo || {add:noop};
		var	comboPending = 0,
			combosPending = [],
			comboPendingTimer = null;
	}


	// build the loader machinery iaw configuration, including has feature tests  依据配置数据, 和特征检测,建立加载器的核功能
	var	injectDependencies = function(module){
			// checkComplete!=0 holds the idle signal; we're not idle if we're injecting dependencies
            /*
                方法签名:需要注入依赖链接的模块对像
             */

			guardCheckComplete(function(){
				forEach(module.deps, injectModule);
				if(has("dojo-combo-api") && comboPending && !comboPendingTimer){
					comboPendingTimer = setTimeout(function() {
						comboPending = 0;
						comboPendingTimer = null;
						req.combo.done(function(mids, url) {
							var onLoadCallback= function(){
								// defQ is a vector of module definitions 1-to-1, onto mids
								runDefQ(0, mids);
								checkComplete();
							};
							combosPending.push(mids);
							injectingModule = mids;
							req.injectUrl(url, onLoadCallback, mids);
							injectingModule = 0;
						}, req);
					}, 0);
				}
			});
		},

		contextRequire = function(a1, a2, a3, referenceModule, contextRequire){
            /*
                a1: 一般为config object, 配置对像, 可以通过require对loader进行配置.
                a2: 函数的依赖数组。
                a3: 加载完依赖后,要执行的回调函数
                referenceModule: 引用模块,举例来说,如dom.js里面, define(["./sniff","./_base/window"], 当要加载dom模块时,需要先加载sniff及 _base/window, 那么在这时相对于sniff时,引用模块就是dojo/dom, 而dom的引用模块是全局函数require,那么引用模块为0
                contextRequire: 加载器的上下文,如果是在全局中使用require来加载模块,那么contextRequire为require函数。如果在定义模块时,如定义一个"my/app", define(['dom','require'],function(dom,require){require(require('dojo/on')}),即使用局部加载器,那么contextRequire为"my/app"模块的require函数, 上下文是通过createContext方法创建。
             */
			var module, syntheticMid;
            /*
               1. a1 如果是一个模块mid字符串,则返回相对应的模块
               2. a1 不是字符串,也不是数组(依赖或者要加载的模块列表。则为配置对像或者函数。require({ aliases:[["text", "dojo/text"]]})
               3. a1 第一个参数为数组,代表直接要加载的模块。
             */
			if(isString(a1)){

				// 方法签名是一个模块模块标识符,用于检测模块是否在Modules
                /*
                    getModule获得一个模块信息,第一个参数是模块 MID, 第二个为引用模块(在模块标识符解析时,如果模块标识符为相对路径,那么先获得引用模块的路径,在计算相对路径)。 第三个参数是指, mid是否存在Modules, 如果没有存在,则立即反回false,而不会把空上模块信息添加到Modules.
                 */
				module = getModule(a1, referenceModule, true);
				if(module && module.executed){
					return module.result;
				}
				throw makeError("undefinedModule", a1);
			}
			if(!isArray(a1)){

				// a1 是一个配置对像, 调用上一节讲过的配置机制中的config方法。
				config(a1, 0, referenceModule);

				// 处理参数, 如果a2为请求加载的模块列表,a3为回调函数,而a1经过上一步骤已经完成了配置,将 a2,a3的位置提前, 方便下一步的处理; (a2, a3) may be (dependencies, callback)
				a1 = a2;
				a2 = a3;
			}
			if(isArray(a1)){
				// 方法签名为 (requestList [,callback])
                // 如果第一个参数为数组(1. require(['dojo/dom',function(){}], 2. require({}, 'dojo/dom',function(){}), 第二种情况会经过上一步处理参数。
				if(!a1.length){
					a2 && a2(); //如果数组为空数组,直接调用回调函数。
				}else{
					syntheticMid = "require*" + uid();  //每一个require都会有一个唯一标识.

					// 解析数组中的模块请求
					for(var mid, deps = [], i = 0; i < a1.length;){
						mid = a1[i++];
						deps.push(getModule(mid, referenceModule));  //根据mid获取一个模块, 并添加到依赖列表,如果是在define中定义的依赖,则referenceModule为定义的模块。referenceModule主要是提供相对路径
					}

					// construct a synthetic module to control execution of the requestList, and, optionally, callback
                    /*
                        构造一个人造模块(不是真实定义的模块),用于控制执行请求的模块链表(deps), 及回调函数。
                        可以这样理解,Dojo loader中都是以模块为操作对象,指定模块的依赖,工厂函数,状态(注入,加载,执行)。 如果仅仅通过 require(['dojo/dom'],function(dom){}), 而不构造一个人造模块。那么需要判断分别为require, define定义不同的加载依赖链接的方法。
                        makeModuleInfo:
                            方法签名(pid, mid, pack, url)
                            return: {pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0};  //executed: 模块未执行, def: 模块的工厂函数
                     */
					module = mix(makeModuleInfo("", syntheticMid, 0, ""), {
						injected: arrived,  //在模块中添加角本注入状态
						deps: deps,  //将 require中的请求链接,做为人造模块的依赖链表
						def: a2 || noop,  //如果有指定回调函数,则工厂函数为回调函数,否则为空函数
						require: referenceModule ? referenceModule.require : req,  // 指定加载器。如有指定了引用模块,那么调用局部加载器。如果没有指定,调用全局加载器
						gc: 1 //garbage collect  垃级回收。在加载完依赖和执行完回调函数后,销毁这个构造的模块。
					});

                    //将构造的这个模块添加到模块数组中.(modules 变量在上一节的配置机制里定义)
					modules[module.mid] = module;

					// checkComplete!=0 holds the idle signal; we're not idle if we're injecting dependencies
                    /*
                        注入依赖列表。
                     */

					injectDependencies(module);

					// try to immediately execute
					// if already traversing a factory tree, then strict causes circular dependency to abort the execution; maybe
					// it's possible to execute this require later after the current traversal completes and avoid the circular dependency.
					// ...but *always* insist on immediate in synch mode
                    /*
                        尝试直接运行模块的工厂函数。
                         strict 主要是在遍历工厂函数时(checkComplete), 避免循环依赖.可以通过下面的例子来理解.

                         require(['my/app'],function(app){

                         })
                         ** my/app.js
                         define(['./app1'],function(app1){
                         require(['dojo/dom'])
                         console.log('a')
                         })
                         ** my/app1.js
                         define(['require'],function(require){
                         require(["./app"])

                         })

                        第一个require因为没有遍历到工厂函数组成的树对像。即还没有调用checkComplete(), 所以checkCompleteGuard为0
                        第二个 require, 即app1.js里面的require. 因为正在处于app1 模块的树对像,所以checkCompleteGuard 为 1. 可以查看 checkComplete().
                        checkComplete 会在guardCheckComplete中,先将checkCompleteGuard++, 然后遍历所有的exeQ中的模块,并执行模块execModule(运行工厂方法)。当执行到app1模块时,发现require(["./app"]).
                        那么就会调用require方法,构造一个人造模块require*_1 此时checkCompleteGuard为1, leagacyMode!=sync.所以 strict 为1.

                        在执行require*_1时,exeModule要先执行my/app模块的工厂函数

                     */

					var strict = checkCompleteGuard && legacyMode!=sync;
					guardCheckComplete(function(){

						execModule(module, strict);
					});
					if(!module.executed){
						// some deps weren't on board or circular dependency detected and strict; therefore, push into the execQ
						execQ.push(module);
					}
					checkComplete();
				}
			}
			return contextRequire;
		},

		createRequire = function(module){

			if(!module){
				return req;
			}

			var result = module.require;
			if(!result){
				result = function(a1, a2, a3){
					return contextRequire(a1, a2, a3, module, result);
				};
				module.require = mix(result, req);
				result.module = module;
				result.toUrl = function(name){
					return toUrl(name, module);
				};
				result.toAbsMid = function(mid){
					return toAbsMid(mid, module);
				};
				if(has("dojo-undef-api")){
					result.undef = function(mid){
						req.undef(mid, module);
					};
				}
				if(has("dojo-sync-loader")){
					result.syncLoadNls = function(mid){
						var nlsModuleInfo = getModuleInfo(mid, module),
							nlsModule = modules[nlsModuleInfo.mid];
						if(!nlsModule || !nlsModule.executed){
							cached = cache[nlsModuleInfo.mid] || cache[urlKeyPrefix + nlsModuleInfo.url];
							if(cached){
								evalModuleText(cached);
								nlsModule = modules[nlsModuleInfo.mid];
							}
						}
						return nlsModule && nlsModule.executed && nlsModule.result;
					};
				}

			}

			return result;
		},

		execQ =
			// The list of modules that need to be evaluated.
			[],

		defQ =
			// 每次调用了define函数后,都会把这个define函数的参数,组成一个数组,添加到defQ中,格式为:[mid, dependencs, facotry], 然后在runDefQ中会弹出数组的元素。
			[],

		waiting =
			// The set of modules upon which the loader is waiting for definition to arrive
			{},

		setRequested = function(module){
			module.injected = requested;
			waiting[module.mid] = 1;
			if(module.url){
				waiting[module.url] = module.pack || 1;
			}
			startTimer();
		},

		setArrived = function(module){
			module.injected = arrived;
			delete waiting[module.mid];
			if(module.url){
				delete waiting[module.url];
			}
			if(isEmpty(waiting)){
				clearTimer();
				has("dojo-sync-loader") && legacyMode==xd && (legacyMode = sync);
			}
		},

		execComplete = req.idle =
			// says the loader has completed (or not) its work
			function(){
				return !defQ.length && isEmpty(waiting) && !execQ.length && !checkCompleteGuard;
			},

		runMapProg = function(targetMid, map){

			// search for targetMid in map; return the map item if found; falsy otherwise
			if(map){
			for(var i = 0; i < map.length; i++){
				if(map[i][2].test(targetMid)){
					return map[i];
				}
			}
			}
			return 0;
		},

		compactPath = function(path){

            /*
                路径处理函数,返回一个简洁的路径
                传入的path可能为 MID, 或者referenceModule+".." +MID;

             */

			var result = [],
				segment, lastSegment;
			path = path.replace(/\\/g, '/').split('/'); // 将path中的反斜线,夫换为 linux下的路径形式
			while(path.length){
                /*
                    1. path 中包含 ".." 时去除掉  紧靠 ".." 的路径 如 dojo/dom/../sniff 会变为dojo/sniff;
                    2. path 中包含 "." 时,直接跳过
                 */
				segment = path.shift();
				if(segment==".." && result.length && lastSegment!=".."){
					result.pop();
					lastSegment = result[result.length - 1];
				}else if(segment!="."){
					result.push(lastSegment= segment);
				} // else ignore "."
			}
			return result.join("/");
		},

		makeModuleInfo = function(pid, mid, pack, url){
            /*
                方法签名:
                    pid: String, 包的标识符
                    mid: String,模块标识符
                    pack: Object, 包的详细信息 { main="main", name="dojo", location="lib/dojo"}
                    url: String, 模块解析后的地址

                return: Object
                   {pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0};  //executed: 模块未执行, def: 模块的工厂函数
             */

			if(has("dojo-sync-loader")){
				var xd= req.isXdUrl(url);

				return {pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0, isXd:xd, isAmd:!!(xd || (packs[pid] && packs[pid].isAmd))};
			}else{
				return {pid:pid, mid:mid, pack:pack, url:url, executed:0, def:0};  //executed: 模块未执行, def: 模块的工厂函数
			}
		},

		getModuleInfo_ = function(mid, referenceModule, packs, modules, baseUrl, mapProgs, pathsMapProg, aliases, alwaysCreate){

            // 模块标识符解析函数, 将一个字符解析为实际的路径。
            // 通过传递参数来代替使用词法变量(查看词法作用域,例如,不是modules, 不是直接访问loader的本地变量), 使得 getModuleInfo_ 可以被loader单独使用(例如, builder)
            // 在这种情况下 (builder) 会非常有用。 getModuleInfo 不会返回已以存在的模块引用, 而总是创建一个新的。
            // arguments are passed instead of using lexical variables so that this function my be used independent of the loader (e.g., the builder)
            // alwaysCreate is useful in this case so that getModuleInfo never returns references to real modules owned by the loader

            /*
             mid: String 模块Id标识符
             referenceModule: Object 参考模块
             packs:  Dojo Config里面的packages包对象。
             modules: 已经存在的模块,
             baseUrl: 根路径,
             mapProgs: 配置变量map或者packageMap,
             pathsMapProg: 配置变量paths,
             aliases 配置变量aliases的对象
             alwaysCreate: 如果指定为true, 一直创建新的文档,默认为undefined.
             */


           /*


            当指定了map对象时,
            packages:[
            {name:"dojo16", location:"lib/dojo16"},
            {name:"dijit16", location:"lib/dijit16"},
            {name:"dojox16", location:"lib/dojox16"},
            {name:"dojo",location:"lib/dojo"},
            {name:"myOldApp",location:"myOldApp"},
            {name:"my", location:"my"}

            ],
            map:{
            myOldApp:{
            dojo:"dojo16",
            dijit:"dijit16",
            dojox:"dojox16"
            }
            }
            输出如下, console.log(mapProgs)
            [["myOldApp", [["dijit", "dijit16", RegExp /^dijit(\/|$)/, 5], ["dojox", "dojox16", RegExp /^dojox(\/|$)/, 5], ["dojo", "dojo16", RegExp /^dojo(\/|$)/, 4]], RegExp /^myOldApp(\/|$)/, 8]]




             */

			var pid, pack, midInPackage, mapItem, url, result, isRelative, requestedMid;
			requestedMid = mid;

			isRelative = /^\./.test(mid);  // 以  "." 开头的mid为相对路径,主要用于package.
			if(/(^\/)|(\:)|(\.js$)/.test(mid) || (isRelative && !referenceModule)){
				// absolute path or protocol of .js filetype, or relative path but no reference module and therefore relative to page
				// whatever it is, it's not a module but just a URL of some sort
				// note: pid===0 indicates the routine is returning an unmodified mid
                /*
                    绝对路径,以 "/"开头,或者是一种协议如http:,或者是以 .js 结尾的文件类型, 或者是 相对路径( 以 ".")开头,而没有referenceModule.
                    这里请求的可能是任意数量的javascript代码,而不是一AMD规范的模块。
                    注意: 如果 pid===0 标志着程序会返回一个未修改的mid.

                */

				return makeModuleInfo(0, mid, 0, mid);
			}else{
				// relative module ids are relative to the referenceModule; get rid of any dots  以"."开头的对头模块标识是相对于 referenceModule(解释参考术语名词). 而忽略其它的点。



				mid = compactPath(isRelative ? (referenceModule.mid + "/../" + mid) : mid);

				if(/^\./.test(mid)){
                    // require['dojo/../.d/dom'] 不知道这个函数是不是要处理这种极端的问题
					throw makeError("irrationalPath", mid);
				}
				// at this point, mid is an absolute mid

				// map the mid
                /*

                   !**  待解决
                */

				if(referenceModule){
					mapItem = runMapProg(referenceModule.mid, mapProgs);

				}

				mapItem = mapItem || mapProgs.star;
				mapItem = mapItem && runMapProg(mid, mapItem[1]);

				if(mapItem){
					mid = mapItem[1] + mid.substring(mapItem[3]);
					}

				match = mid.match(/^([^\/]+)(\/(.+))?$/);

				pid = match ? match[1] : "";
				if((pack = packs[pid])){
					mid = pid + "/" + (midInPackage = (match[3] || pack.main));
				}else{
                    pid = "";
				}

				// search aliases
				var candidateLength = 0,
					candidate = 0;
				forEach(aliases, function(pair){
					var match = mid.match(pair[0]);
					if(match && match.length>candidateLength){
						candidate = isFunction(pair[1]) ? mid.replace(pair[0], pair[1]) : pair[1];
					}
				});
				if(candidate){
					return getModuleInfo_(candidate, 0, packs, modules, baseUrl, mapProgs, pathsMapProg, aliases, alwaysCreate);
				}

				result = modules[mid];
				if(result){
					return alwaysCreate ? makeModuleInfo(result.pid, result.mid, result.pack, result.url) : modules[mid];
				}
			}
			// get here iff the sought-after module does not yet exist; therefore, we need to compute the URL given the
			// fully resolved (i.e., all relative indicators and package mapping resolved) module id
            /*
                此时 寻找加载的模块还没有存在,因些我们需要对过完全解析的模块id(如,完成所有的相对标识及包映射) 来计算它的URL
            */
			// note: pid!==0 indicates the routine is returning a url that has .js appended unmodified mid 注意: 当 pid!==0时, 表明程序会返回未变的mid, 并在mid后添加.js的URL

			mapItem = runMapProg(mid, pathsMapProg); // 配置变量中的paths.
			if(mapItem){

				url = mapItem[1] + mid.substring(mapItem[3]);
			}else if(pid){
				url = pack.location + "/" + midInPackage;
			}else if(has("config-tlmSiblingOfDojo")){
				url = "../" + mid;
			}else{
				url = mid;
			}
			// if result is not absolute, add baseUrl
			if(!(/(^\/)|(\:)/.test(url))){
				url = baseUrl + url;
			}
			url += ".js";
			return makeModuleInfo(pid, mid, pack, compactPath(url));
		},

		getModuleInfo = function(mid, referenceModule, fromPendingCache){
			return getModuleInfo_(mid, referenceModule, packs, modules, req.baseUrl, fromPendingCache ? [] : mapProgs, fromPendingCache ? [] : pathsMapProg, fromPendingCache ? [] : aliases);
		},

		resolvePluginResourceId = function(plugin, prid, referenceModule){
			return plugin.normalize ? plugin.normalize(prid, function(mid){return toAbsMid(mid, referenceModule);}) : toAbsMid(prid, referenceModule);
		},

		dynamicPluginUidGenerator = 0,

		getModule = function(mid, referenceModule, immediate){

			// compute and optionally construct (if necessary) the module implied by the mid with respect to referenceModule
			var match, plugin, prid, result;
			match = mid.match(/^(.+?)\!(.*)$/);

			if(match){
				// name was <plugin-module>!<plugin-resource-id>
				plugin = getModule(match[1], referenceModule, immediate);

				if(has("dojo-sync-loader") && legacyMode == sync && !plugin.executed){
					injectModule(plugin);
					if(plugin.injected===arrived && !plugin.executed){
						guardCheckComplete(function(){
							execModule(plugin);
						});
					}
					if(plugin.executed){
						promoteModuleToPlugin(plugin);
					}else{
						// we are in xdomain mode for some reason
						execQ.unshift(plugin);
					}
				}



				if(plugin.executed === executed && !plugin.load){
					// executed the module not knowing it was a plugin
					promoteModuleToPlugin(plugin);
				}

				// if the plugin has not been loaded, then can't resolve the prid and  must assume this plugin is dynamic until we find out otherwise
				if(plugin.load){
					prid = resolvePluginResourceId(plugin, match[2], referenceModule);
					mid = (plugin.mid + "!" + (plugin.dynamic ? ++dynamicPluginUidGenerator + "!" : "") + prid);
				}else{
					prid = match[2];
					mid = plugin.mid + "!" + (++dynamicPluginUidGenerator) + "!waitingForPlugin";
				}
				result = {plugin:plugin, mid:mid, req:createRequire(referenceModule), prid:prid};
			}else{

				result = getModuleInfo(mid, referenceModule);

			}


			return modules[result.mid] || (!immediate && (modules[result.mid] = result));
		},

		toAbsMid = req.toAbsMid = function(mid, referenceModule){
            //将给定的mid, 通过模块标识符解析后,获得一个绝的模块标识符。
			return getModuleInfo(mid, referenceModule).mid;
		},

		toUrl = req.toUrl = function(name, referenceModule){
            /*
                 获得一个资源的路径
                 name: string, 由模块标识符作为前缀,如在dojo包内有一张图片,地址为js/lib/dojo/main.jpg, 那么指定的名称可以为 dojo/main.jpg.
                 referenceModule: 哪个模块调用了req.toUrl, 如 my/app.js 里面调用require.toUrl("./main.jpg"), 那么 referenceModule为 my/app;
            */

			var moduleInfo = getModuleInfo(name+"/x", referenceModule),
				url= moduleInfo.url;

			return fixupUrl(moduleInfo.pid===0 ?
				// if pid===0, then name had a protocol or absolute path; either way, toUrl is the identify function in such cases
				name :
				// "/x.js" since getModuleInfo automatically appends ".js" and we appended "/x" to make name look like a module id
				url.substring(0, url.length-5)
			);
		},

		nonModuleProps = {
			injected: arrived,
			executed: executed,
			def: nonmodule,
			result: nonmodule
		},

		makeCjs = function(mid){
			return modules[mid] = mix({mid:mid}, nonModuleProps);
		},

		cjsRequireModule = makeCjs("require"),
		cjsExportsModule = makeCjs("exports"),
		cjsModuleModule = makeCjs("module"),

		runFactory = function(module, args){
			req.trace("loader-run-factory", [module.mid]);
			var factory = module.def,
				result;

			has("dojo-sync-loader") && syncExecStack.unshift(module);
			if(has("config-dojo-loader-catches")){
				try{
					result= isFunction(factory) ? factory.apply(null, args) : factory;
				}catch(e){
					signal(error, module.result = makeError("factoryThrew", [module, e]));
				}
			}else{
				result= isFunction(factory) ? factory.apply(null, args) : factory;
			}


			module.result = result===undefined && module.cjs ? module.cjs.exports : result;
			has("dojo-sync-loader") && syncExecStack.shift(module);
		},

		abortExec = {},

		defOrder = 0,

		promoteModuleToPlugin = function(pluginModule){
			var plugin = pluginModule.result;
			pluginModule.dynamic = plugin.dynamic;
			pluginModule.normalize = plugin.normalize;
			pluginModule.load = plugin.load;
			return pluginModule;
		},

		resolvePluginLoadQ = function(plugin){
			// plugins is a newly executed module that has a loadQ waiting to run

			// step 1: traverse the loadQ and fixup the mid and prid; remember the map from original mid to new mid
			// recall the original mid was created before the plugin was on board and therefore it was impossible to
			// compute the final mid; accordingly, prid may or may not change, but the mid will definitely change
			var map = {};
			forEach(plugin.loadQ, function(pseudoPluginResource){
				// manufacture and insert the real module in modules
				var prid = resolvePluginResourceId(plugin, pseudoPluginResource.prid, pseudoPluginResource.req.module),
					mid = plugin.dynamic ? pseudoPluginResource.mid.replace(/waitingForPlugin$/, prid) : (plugin.mid + "!" + prid),
					pluginResource = mix(mix({}, pseudoPluginResource), {mid:mid, prid:prid, injected:0});
				if(!modules[mid]){
					// create a new (the real) plugin resource and inject it normally now that the plugin is on board
					injectPlugin(modules[mid] = pluginResource);
				} // else this was a duplicate request for the same (plugin, rid) for a nondynamic plugin

				// pluginResource is really just a placeholder with the wrong mid (because we couldn't calculate it until the plugin was on board)
				// mark is as arrived and delete it from modules; the real module was requested above
				map[pseudoPluginResource.mid] = modules[mid];
				setArrived(pseudoPluginResource);
				delete modules[pseudoPluginResource.mid];
			});
			plugin.loadQ = 0;

			// step2: replace all references to any placeholder modules with real modules
			var substituteModules = function(module){
				for(var replacement, deps = module.deps || [], i = 0; i<deps.length; i++){
					replacement = map[deps[i].mid];
					if(replacement){
						deps[i] = replacement;
					}
				}
			};
			for(var p in modules){
				substituteModules(modules[p]);
			}
			forEach(execQ, substituteModules);
		},

		finishExec = function(module){
			req.trace("loader-finish-exec", [module.mid]);
			module.executed = executed;
			module.defOrder = defOrder++;
			has("dojo-sync-loader") && forEach(module.provides, function(cb){ cb(); });
			if(module.loadQ){
				// the module was a plugin
				promoteModuleToPlugin(module);
				resolvePluginLoadQ(module);
			}
			// remove all occurrences of this module from the execQ
			for(i = 0; i < execQ.length;){
				if(execQ[i] === module){
					execQ.splice(i, 1);
				}else{
					i++;
				}
			}
			// delete references to synthetic modules
			if (/^require\*/.test(module.mid)) {
				delete modules[module.mid];
			}
		},

		circleTrace = [],

		execModule = function(module, strict){
			// run the dependency vector, then run the factory for module
            // 运行依赖向量, 然后运行模块的工厂函数
			if(module.executed === executing){
				req.trace("loader-circular-dependency", [circleTrace.concat(module.mid).join("->")]);
				return (!module.def || strict) ? abortExec :  (module.cjs && module.cjs.exports);
			}
			// at this point the module is either not executed or fully executed


			if(!module.executed){
                /*
                    依赖的模块刚刚请求完成,即完成角本注入,module.injected == "requested" 还不是received. 所以 def == 0
                 */
				if(!module.def){

					return abortExec;  //反回 {}
				}
				var mid = module.mid,
					deps = module.deps || [],
					arg, argResult,
					args = [],
					i = 0;

				if(has("dojo-trace-api")){
					circleTrace.push(mid);
					req.trace("loader-exec-module", ["exec", circleTrace.length, mid]);
				}

				// for circular dependencies, assume the first module encountered was executed OK
				// modules that circularly depend on a module that has not run its factory will get
				// the pre-made cjs.exports===module.result. They can take a reference to this object and/or
				// add properties to it. When the module finally runs its factory, the factory can
				// read/write/replace this object. Notice that so long as the object isn't replaced, any
				// reference taken earlier while walking the deps list is still valid.
				module.executed = executing;
				while((arg = deps[i++])){

					argResult = ((arg === cjsRequireModule) ? createRequire(module) :
									((arg === cjsExportsModule) ? module.cjs.exports :
										((arg === cjsModuleModule) ? module.cjs :
											execModule(arg, strict))));

					if(argResult === abortExec){

						module.executed = 0;
						req.trace("loader-exec-module", ["abort", mid]);
						has("dojo-trace-api") && circleTrace.pop();
						return abortExec;
					}
					args.push(argResult);
				}
				runFactory(module, args);
				finishExec(module);
				has("dojo-trace-api") && circleTrace.pop();
			}
			// at this point the module is guaranteed fully executed

			return module.result;
		},


		checkCompleteGuard = 0,

		guardCheckComplete = function(proc){
			try{
				checkCompleteGuard++;
				proc();
			}finally{

				checkCompleteGuard--;
			}
			if(execComplete()){

				signal("idle", []);
			}
		},

		checkComplete = function(){
			// keep going through the execQ as long as at least one factory is executed
			// plugins, recursion, cached modules all make for many execution path possibilities
			if(checkCompleteGuard){
				return;
			}

			guardCheckComplete(function(){
				checkDojoRequirePlugin();
				for(var currentDefOrder, module, i = 0; i < execQ.length;){
					currentDefOrder = defOrder;

					module = execQ[i];
					execModule(module);
					if(currentDefOrder!=defOrder){
						// defOrder was bumped one or more times indicating something was executed (note, this indicates
						// the execQ was modified, maybe a lot (for example a later module causes an earlier module to execute)
						checkDojoRequirePlugin();
						i = 0;
					}else{
						// nothing happened; check the next module in the exec queue
						i++;
					}
				}
			});
		};


	if(has("dojo-undef-api")){
		req.undef = function(moduleId, referenceModule){
			// In order to reload a module, it must be undefined (this routine) and then re-requested. 为了重新加载一个模块, 它必须是未定义的(当前程序), 然后在重新加载。
			// This is useful for testing frameworks (at least). 至少在测试框架时很有用。
			var module = getModule(moduleId, referenceModule);
			setArrived(module);
			mix(module, {def:0, executed:0, injected:0, node:0});
		};
	}
    // 以下是支持加载跨域的模块(支持加载不同域名下的模块)。
	if(has("dojo-inject-api")){


		if(has("dojo-loader-eval-hint-url")===undefined){
			has.add("dojo-loader-eval-hint-url", 1);
		}

		var fixupUrl= function(url){
				url += ""; // make sure url is a Javascript string (some paths may be a Java string)
				return url + (cacheBust ? ((/\?/.test(url) ? "&" : "?") + cacheBust) : "");
			},

			injectPlugin = function(
				module
			){
				// injects the plugin module given by module; may have to inject the plugin itself
				var plugin = module.plugin;

				if(plugin.executed === executed && !plugin.load){
					// executed the module not knowing it was a plugin
					promoteModuleToPlugin(plugin);
				}

				var onLoad = function(def){
						module.result = def;
						setArrived(module);
						finishExec(module);
						checkComplete();
					};

				if(plugin.load){
					plugin.load(module.prid, module.req, onLoad);
				}else if(plugin.loadQ){
					plugin.loadQ.push(module);
				}else{
					// the unshift instead of push is important: we don't want plugins to execute as
					// dependencies of some other module because this may cause circles when the plugin
					// loadQ is run; also, generally, we want plugins to run early since they may load
					// several other modules and therefore can potentially unblock many modules
					plugin.loadQ = [module];
					execQ.unshift(plugin);
					injectModule(plugin);
				}
			},

			// for IE, injecting a module may result in a recursive execution if the module is in the cache

			cached = 0,

			injectingModule = 0,//表示当前正在注入的模块对像, 即正在创建一个script元素,并把它添到insertPointSibling之前。添加完后把injectingModule赋值回 0.

			injectingCachedModule = 0,

			evalModuleText = function(text, module){

				// see def() for the injectingCachedModule bracket; it simply causes a short, safe circuit
				if(has("config-stripStrict")){
					text = text.replace(/"use strict"/g, '');

				}

				injectingCachedModule = 1;
				if(has("config-dojo-loader-catches")){
					try{
						if(text===cached){
							cached.call(null);
						}else{
							req.eval(text, has("dojo-loader-eval-hint-url") ? module.url : module.mid);
						}
					}catch(e){
						signal(error, makeError("evalModuleThrew", module));
					}
				}else{
					if(text===cached){
						cached.call(null);
					}else{
						req.eval(text, has("dojo-loader-eval-hint-url") ? module.url : module.mid);
					}
				}
				injectingCachedModule = 0;
			},

			injectModule = function(module){

				// Inject the module. In the browser environment, this means appending a script element into
				// the document; in other environments, it means loading a file.
				//
				// If in synchronous mode, then get the module synchronously if it's not xdomainLoading.
                /*
                    注入模块。 在浏览器环境下, 它会在文档中添加一个script 元素, 在其它环境下,它会直接加载一个文件。
                    如果是同步模式下, 而没有指定跨域加载,则会以同步的方法获得一个模块。

                 */
				var mid = module.mid,
					url = module.url;
				if(module.executed || module.injected || waiting[mid] || (module.url && ((module.pack && waiting[module.url]===module.pack) || waiting[module.url]==1))){
					return;
				}

				setRequested(module);  //设置inject的状态为requested, 并将模块添加到waiting对像中。表明这个模块正在等到下载(从服务器请求这个模块文件)
                // dojo-combo-api 已弃用
				if(has("dojo-combo-api")){
					var viaCombo = 0;
					if(module.plugin && module.plugin.isCombo){
						// a combo plugin; therefore, must be handled by combo service
						// the prid should have already been converted to a URL (if required by the plugin) during
						// the normalize process; in any event, there is no way for the loader to know how to
						// to the conversion; therefore the third argument is zero
						req.combo.add(module.plugin.mid, module.prid, 0, req);
						viaCombo = 1;
					}else if(!module.plugin){
						viaCombo = req.combo.add(0, module.mid, module.url, req);
					}
					if(viaCombo){
						comboPending= 1;
						return;
					}
				}
                // 如果模块为插件,比如dojo/text!my/app.html, getModule方法会返回一个包含 plugin属性的对像,了解更多,请查看getModule方法。
				if(module.plugin){
					injectPlugin(module);
					return;
				} // else a normal module (not a plugin)

                // 模块在加载完成后的回调函数。
				var onLoadCallback = function(){
					runDefQ(module); //运行模块的工厂函数

					if(module.injected !== arrived){
						// the script that contained the module arrived and has been executed yet
						// nothing was added to the defQ (so it wasn't an AMD module) and the module
						// wasn't marked as arrived by dojo.provide (so it wasn't a v1.6- module);
						// therefore, it must not have been a module; adjust state accordingly
						if(has("dojo-enforceDefine")){
							signal(error, makeError("noDefine", module));
							return;
						}
						setArrived(module);

						mix(module, nonModuleProps);
						req.trace("loader-define-nonmodule", [module.url]);
					}

					if(has("dojo-sync-loader") && legacyMode){
						// must call checkComplete even in for sync loader because we may be in xdomainLoading mode;
						// but, if xd loading, then don't call checkComplete until out of the current sync traversal
						// in order to preserve order of execution of the dojo.required modules
						!syncExecStack.length && checkComplete();
					}else{
						checkComplete();
					}
				};
                /*
                    cache 对像是的配置机制中定义的对像,用于缓存已加载的资源。可以查看cache变量声明时的注释。
                 */
				cached = cache[mid] || cache[urlKeyPrefix + module.url];
				if(cached){

					req.trace("loader-inject", ["cache", module.mid, url]);
					evalModuleText(cached, module);
					onLoadCallback();
					return;
				}

				if(has("dojo-sync-loader") && legacyMode){

					if(module.isXd){
						// switch to async mode temporarily; if current legacyMode!=sync, then is must be one of {legacyAsync, xd, false}
						legacyMode==sync && (legacyMode = xd);
						// fall through and load via script injection
					}else if(module.isAmd && legacyMode!=sync){
						// fall through and load via script injection
					}else{
						// mode may be sync, xd/legacyAsync, or async; module may be AMD or legacy; but module is always located on the same domain
						var xhrCallback = function(text){
							if(legacyMode==sync){
								// the top of syncExecStack gives the current synchronously executing module; the loader needs
								// to know this if it has to switch to async loading in the middle of evaluating a legacy module
								// this happens when a modules dojo.require's a module that must be loaded async because it's xdomain
								// (using unshift/shift because there is no back() methods for Javascript arrays)
								syncExecStack.unshift(module);
								evalModuleText(text, module);
								syncExecStack.shift();

								// maybe the module was an AMD module
								runDefQ(module);

								// legacy modules never get to defineModule() => cjs and injected never set; also evaluation implies executing
								if(!module.cjs){
									setArrived(module);
									finishExec(module);
								}

								if(module.finish){
									// while synchronously evaluating this module, dojo.require was applied referencing a module
									// that had to be loaded async; therefore, the loader stopped answering all dojo.require
									// requests so they could be answered completely in the correct sequence; module.finish gives
									// the list of dojo.requires that must be re-applied once all target modules are available;
									// make a synthetic module to execute the dojo.require's in the correct order

									// compute a guaranteed-unique mid for the synthetic finish module; remember the finish vector; remove it from the reference module
									// TODO: can we just leave the module.finish...what's it hurting?
									var finishMid = mid + "*finish",
										finish = module.finish;
									delete module.finish;

									def(finishMid, ["dojo", ("dojo/require!" + finish.join(",")).replace(/\./g, "/")], function(dojo){
										forEach(finish, function(mid){ dojo.require(mid); });
									});
									// unshift, not push, which causes the current traversal to be reattempted from the top
									execQ.unshift(getModule(finishMid));
								}
								onLoadCallback();
							}else{

								text = transformToAmd(module, text);
								if(text){
									evalModuleText(text, module);
									onLoadCallback();
								}else{
									// if transformToAmd returned falsy, then the module was already AMD and it can be script-injected
									// do so to improve debugability(even though it means another download...which probably won't happen with a good browser cache)
									injectingModule = module;
									req.injectUrl(fixupUrl(url), onLoadCallback, module);
									injectingModule = 0;
								}
							}
						};

						req.trace("loader-inject", ["xhr", module.mid, url, legacyMode!=sync]);
						if(has("config-dojo-loader-catches")){
							try{
								req.getText(url, legacyMode!=sync, xhrCallback);
							}catch(e){
								signal(error, makeError("xhrInjectFailed", [module, e]));
							}
						}else{
							req.getText(url, legacyMode!=sync, xhrCallback);
						}
						return;
					}
				} // else async mode or fell through in xdomain loading mode; either way, load by script injection
				req.trace("loader-inject", ["script", module.mid, url]);
				injectingModule = module;
				req.injectUrl(fixupUrl(url), onLoadCallback, module);
				injectingModule = 0;
			},

			defineModule = function(module, deps, def){

                /*
                    定义一个模块
                        1. 跟def函数的区别:def函数即全局define函数。只是构造一个数组[mid, dependences, factory], 并添加到defQ中。
                        2. 跟getModule的区别, getModule是获得一个对像 {pid="dojo", mid="dojo/dom", pack={...}, more...} 并把这个对像存入到 modules对像中, 而没有依赖deps,工厂函数def。
                 */
				req.trace("loader-define-module", [module.mid, deps]);

				if(has("dojo-combo-api") && module.plugin && module.plugin.isCombo){
					// the module is a plugin resource loaded by the combo service
					// note: check for module.plugin should be enough since normal plugin resources should
					// not follow this path; module.plugin.isCombo is future-proofing belt and suspenders
					module.result = isFunction(def) ? def() : def;
					setArrived(module);
					finishExec(module);
					return module;
				}

				var mid = module.mid;
				if(module.injected === arrived){
					signal(error, makeError("multipleDefine", module));
					return module;
				}
				mix(module, {
					deps: deps,
					def: def,
					cjs: {  //CommonJS
						id: module.mid,
						uri: module.url,
						exports: (module.result = {}),
						setExports: function(exports){
							module.cjs.exports = exports;
						},
						config:function(){
							return module.config;
						}
					}
				});

				// resolve deps with respect to this module
				for(var i = 0; deps[i]; i++){
					deps[i] = getModule(deps[i], module);
				}
                //  可以忽略
				if(has("dojo-sync-loader") && legacyMode && !waiting[mid]){

					// the module showed up without being asked for; it was probably in a <script> element
					injectDependencies(module);
					execQ.push(module);
					checkComplete();
				}
                //设置模块为到达状态
				setArrived(module);

				if(!isFunction(def) && !deps.length){
					module.result = def;
					finishExec(module);
				}

				return module;
			},

			runDefQ = function(referenceModule, mids){
				// defQ is an array of [id, dependencies, factory]
				// mids (if any) is a vector of mids given by a combo service
				var definedModules = [],
					module, args;
                /*
                    defQ: 是每次调用了define函数后,都会把这个define函数的参数,组成一个数组,添加到defQ中,格式为:[mid, dependenceies, factory]
                    defQ.length 为 1, 因为每次运行define之后,会把[mid, dependencies, factory]添加到defQ中,length为 1。 之后调用injectModule的 onLoadCallback中调用 runDefQ(模块对像). defQ.shift()之后,defQ变为空数组。
                 */

				while(defQ.length){
					args = defQ.shift();
					mids && (args[0]= mids.shift());
					// explicit define indicates possible multiple modules in a single file; delay injecting dependencies until defQ fully
					// processed since modules earlier in the queue depend on already-arrived modules that are later in the queue
					// TODO: what if no args[0] and no referenceModule
                    /*
                        如果在define 的第一个参数没有指定为字符串(模块名), 那么就使用 injectModule中传入的模块对像。
                     */
					module = (args[0] && getModule(args[0])) || referenceModule;
					definedModules.push([module, args[1], args[2]]);
				}
                // 可以忽略, 因为只在配置中指定了cache才有效。
				consumePendingCacheInsert(referenceModule);
				forEach(definedModules, function(args){
                    /*
                        先定义这个模块。defindModule会给 referenceModule对像添加  deps, def两个属性。并返回模块对像。之后通过injectDependencies加载依赖
                     */
					injectDependencies(defineModule.apply(null, args));
				});
			};
	}

	var timerId = 0,
		clearTimer = noop,
		startTimer = noop;
	if(has("dojo-timeout-api")){
		// Timer machinery that monitors how long the loader is waiting and signals an error when the timer runs out.
        // 配合 waitSeconds 配置,提交一个模块请求后,触发一个setTimeout, 如果在这个时间内没有加载完模块,并调用clearTimer, 则触发一个加载错误。
		clearTimer = function(){
			timerId && clearTimeout(timerId);
			timerId = 0;
		};

		startTimer = function(){
			clearTimer();
			if(req.waitms){
				timerId = window.setTimeout(function(){
					clearTimer();
					signal(error, makeError("timeout", waiting));
				}, req.waitms);
			}
		};
	}

	if (has("dom")) {
		// Test for IE's different way of signaling when scripts finish loading.  Note that according to
		// http://bugs.dojotoolkit.org/ticket/15096#comment:14, IE9 also needs to follow the
		// IE specific code path even though it has an addEventListener() method.
		// Unknown if special path needed on IE10+, which also has a document.attachEvent() method.
		// Should evaluate to false for Opera and Windows 8 apps, even though they document.attachEvent()
		//  is defined in both those environments.
		has.add("ie-event-behavior", doc.attachEvent && typeof Windows === "undefined" &&
			(typeof opera === "undefined" || opera.toString() != "[object Opera]"));
	}

	if(has("dom") && (has("dojo-inject-api") || has("dojo-dom-ready-api"))){
		var domOn = function(node, eventName, ieEventName, handler){
				// Add an event listener to a DOM node using the API appropriate for the current browser;
				// return a function that will disconnect the listener.
				if(!has("ie-event-behavior")){
					node.addEventListener(eventName, handler, false);
					return function(){
						node.removeEventListener(eventName, handler, false);
					};
				}else{
					node.attachEvent(ieEventName, handler);
					return function(){
						node.detachEvent(ieEventName, handler);
					};
				}
			},
			windowOnLoadListener = domOn(window, "load", "onload", function(){
				req.pageLoaded = 1;
				doc.readyState!="complete" && (doc.readyState = "complete");
				windowOnLoadListener();
			});

		if(has("dojo-inject-api")){
			// if the loader is on the page, there must be at least one script element
			// getting its parent and then doing insertBefore solves the "Operation Aborted"
			// error in IE from appending to a node that isn't properly closed; see
			// dojo/tests/_base/loader/requirejs/simple-badbase.html for an example
			// don't use scripts with type dojo/... since these may be removed; see #15809
			// prefer to use the insertPoint computed during the config sniff in case a script is removed; see #16958
			var scripts = doc.getElementsByTagName("script"),
				i = 0,
				script;
			while(!insertPointSibling){
				if(!/^dojo/.test((script = scripts[i++]) && script.type)){
					insertPointSibling= script;
				}
			}

			req.injectUrl = function(url, callback, owner){
                /*
                    插入script元素到insert-point之前。 script的src的属性值为 url;
                    加载完成后调用callback.
                    ower的值为模块对像。
                 */

				var node = owner.node = doc.createElement("script"),
					onLoad = function(e){
						e = e || window.event;
						var node = e.target || e.srcElement;
						if(e.type === "load" || /complete|loaded/.test(node.readyState)){
							loadDisconnector();  //注销load监听器
							errorDisconnector(); //注销error监听器
							callback && callback();
						}
					},
					loadDisconnector = domOn(node, "load", "onreadystatechange", onLoad),  // 添加一个监听器,并返回一个注销监听器的方法
					errorDisconnector = domOn(node, "error", "onerror", function(e){
						loadDisconnector();
						errorDisconnector();
						signal(error, makeError("scriptError", [url, e]));
					});

				node.type = "text/javascript";
				node.charset = "utf-8";
				node.src = url;
				insertPointSibling.parentNode.insertBefore(node, insertPointSibling);
				return node;
			};
		}
	}

	if(has("dojo-log-api")){
		req.log = function(){
            // 等于console.log, 每一个被传递的值会被单独到输出到一行里。

			try{
				for(var i = 0; i < arguments.length; i++){
					console.log(arguments[i]);
				}
			}catch(e){}
		};
	}else{
		req.log = noop;
	}

	if(has("dojo-trace-api")){
		var trace = req.trace = function(
			group,	// the trace group to which this application belongs
			args	// the contents of the trace
		){
			///
			// Tracing interface by group.
			//
			// Sends the contents of args to the console iff (req.trace.on && req.trace[group])

			if(trace.on && trace.group[group]){
				signal("trace", [group, args]);
				for(var arg, dump = [], text= "trace:" + group + (args.length ? (":" + args[0]) : ""), i= 1; i<args.length;){
					arg = args[i++];
					if(isString(arg)){
						text += ", " + arg;
					}else{
						dump.push(arg);
					}
				}
				req.log(text);
				dump.length && dump.push(".");
				req.log.apply(req, dump);
			}
		};
		mix(trace, {
			on:1,
			group:{},
			set:function(group, value){
				if(isString(group)){
					trace.group[group]= value;
				}else{
					mix(trace.group, group);
				}
			}
		});
		trace.set(mix(mix(mix({}, defaultConfig.trace), userConfig.trace), dojoSniffConfig.trace));
		on("config", function(config){

			config.trace && trace.set(config.trace);
		});
	}else{
		req.trace = noop;
	}

	var def = function(
		mid,		  //(commonjs.moduleId, optional)
		dependencies, //(array of commonjs.moduleId, optional) list of modules to be loaded before running factory
		factory		  //(any)
	){
		///
		// Advises the loader of a module factory. //Implements http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition.
		///
		//note
		// CommonJS factory scan courtesy of http://requirejs.org

		var arity = arguments.length,
			defaultDeps = ["require", "exports", "module"],
			// the predominate signature...
			args = [0, mid, dependencies];
		if(arity==1){
			args = [0, (isFunction(mid) ? defaultDeps : []), mid];
		}else if(arity==2 && isString(mid)){
			args = [mid, (isFunction(dependencies) ? defaultDeps : []), dependencies];
		}else if(arity==3){
			args = [mid, dependencies, factory];
		}

		if(has("dojo-amd-factory-scan") && args[1]===defaultDeps){
			args[2].toString()
				.replace(/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg, "")
				.replace(/require\(["']([\w\!\-_\.\/]+)["']\)/g, function(match, dep){
				args[1].push(dep);  // 如果是AMD 工厂函数,默认加入 require, exports, module.
			});
		}

		req.trace("loader-define", args.slice(0, 2));

		var targetModule = args[0] && getModule(args[0]),
			module;

		if(targetModule && !waiting[targetModule.mid]){
			// given a mid that hasn't been requested; therefore, defined through means other than injecting
			// consequent to a require() or define() application; examples include defining modules on-the-fly
			// due to some code path or including a module in a script element. In any case,
			// there is no callback waiting to finish processing and nothing to trigger the defQ and the
			// dependencies are never requested; therefore, do it here.
			injectDependencies(defineModule(targetModule, args[1], args[2]));
		}else if(!has("ie-event-behavior") || !has("host-browser") || injectingCachedModule){
			// not IE path: anonymous module and therefore must have been injected; therefore, onLoad will fire immediately
			// after script finishes being evaluated and the defQ can be run from that callback to detect the module id

			defQ.push(args);
		}else{
			// IE path: possibly anonymous module and therefore injected; therefore, cannot depend on 1-to-1,
			// in-order exec of onLoad with script eval (since it's IE) and must manually detect here
			targetModule = targetModule || injectingModule;
			if(!targetModule){
				for(mid in waiting){
					module = modules[mid];
					if(module && module.node && module.node.readyState === 'interactive'){
						targetModule = module;
						break;
					}
				}
				if(has("dojo-combo-api") && !targetModule){
					for(var i = 0; i<combosPending.length; i++){
						targetModule = combosPending[i];
						if(targetModule.node && targetModule.node.readyState === 'interactive'){
							break;
						}
						targetModule= 0;
					}
				}
			}
			if(has("dojo-combo-api") && isArray(targetModule)){
				injectDependencies(defineModule(getModule(targetModule.shift()), args[1], args[2]));
				if(!targetModule.length){
					combosPending.splice(i, 1);
				}
			}else if(targetModule){
				consumePendingCacheInsert(targetModule);
				injectDependencies(defineModule(targetModule, args[1], args[2]));
			}else{
				signal(error, makeError("ieDefineFailed", args[0]));
			}
			checkComplete();
		}
	};
	def.amd = {
		vendor:"dojotoolkit.org"
	};

	if(has("dojo-requirejs-api")){
		req.def = def;
	}

	// allow config to override default implementation of named functions; this is useful for
	// non-browser environments, e.g., overriding injectUrl, getText, log, etc. in node.js, Rhino, etc.
	// also useful for testing and monkey patching loader
	mix(mix(req, defaultConfig.loaderPatch), userConfig.loaderPatch);

	// now that req is fully initialized and won't change, we can hook it up to the error signal
	on(error, function(arg){
		try{
			console.error(arg);
			if(arg instanceof Error){
				for(var p in arg){
					console.log(p + ":", arg[p]);
				}
				console.log(".");
			}
		}catch(e){}
	});

	// always publish these
	mix(req, {
		uid:uid,
		cache:cache,
		packs:packs
	});

    // 禁止loader向外提供更多信息, build时会设置为0
	if(has("dojo-publish-privates")){

		mix(req, {
			// these may be interesting to look at when debugging
			paths:paths,
			aliases:aliases,
			modules:modules,
			legacyMode:legacyMode,
			execQ:execQ,
			defQ:defQ,
			waiting:waiting,

			// these are used for testing
			// TODO: move testing infrastructure to a different has feature
			packs:packs,
			mapProgs:mapProgs,
			pathsMapProg:pathsMapProg,
			listenerQueues:listenerQueues,

			// these are used by the builder (at least)
			computeMapProg:computeMapProg,
			computeAliases:computeAliases,
			runMapProg:runMapProg,
			compactPath:compactPath,
			getModuleInfo:getModuleInfo_
		});
	}

	// the loader can be defined exactly once; look for global define which is the symbol AMD loaders are
	// *required* to define (as opposed to require, which is optional)
	if(global.define){
		if(has("dojo-log-api")){
			signal(error, makeError("defineAlreadyDefined", 0));
		}
		return;
	}else{
		global.define = def;
		global.require = req;
		if(has("host-node")){
			require = req;
		}
	}

	if(has("dojo-combo-api") && req.combo && req.combo.plugins){
		var plugins = req.combo.plugins,
			pluginName;
		for(pluginName in plugins){
			mix(mix(getModule(pluginName), plugins[pluginName]), {isCombo:1, executed:"executed", load:1});
		}
	}

	if(has("dojo-config-api")){
		forEach(delayedModuleConfig, function(c){ config(c); });
		var bootDeps = dojoSniffConfig.deps ||	userConfig.deps || defaultConfig.deps,
			bootCallback = dojoSniffConfig.callback || userConfig.callback || defaultConfig.callback;
		req.boot = (bootDeps || bootCallback) ? [bootDeps || [], bootCallback] : 0;
	}
	if(!has("dojo-built")){
		!req.async && req(["dojo"]);
		req.boot && req.apply(null, req.boot);
	}
})
//>>excludeStart("replaceLoaderConfig", kwArgs.replaceLoaderConfig);
(
	// userConfig
	(function(){
		// make sure we're looking at global dojoConfig etc.
		return this.dojoConfig || this.djConfig || this.require || {};
	})(),

	// defaultConfig
	{
		// the default configuration for a browser; this will be modified by other environments
		hasCache:{
			"host-browser":1,
			"dom":1,
			"dojo-amd-factory-scan":1,
			"dojo-loader":1,
			"dojo-has-api":1,
			"dojo-inject-api":1,
			"dojo-timeout-api":1,
			"dojo-trace-api":1,
			"dojo-log-api":1,
			"dojo-dom-ready-api":1,
			"dojo-publish-privates":1,
			"dojo-config-api":1,
			"dojo-sniff":1,
			"dojo-sync-loader":1,
			"dojo-test-sniff":1,
			"config-deferredInstrumentation":1,
			"config-useDeferredInstrumentation":"report-unhandled-rejections",
			"config-tlmSiblingOfDojo":1
		},
		packages:[{
			// note: like v1.6-, this bootstrap computes baseUrl to be the dojo directory
			name:'dojo',
			location:'.'
		},{
			name:'tests',
			location:'./tests'
		},{
			name:'dijit',
			location:'../dijit'
		},{
			name:'build',
			location:'../util/build'
		},{
			name:'doh',
			location:'../util/doh'
		},{
			name:'dojox',
			location:'../dojox'
		},{
			name:'demos',
			location:'../demos'
		}],
		trace:{
			// 在加载一个模块时,需要跟踪哪些信息, 先设置以下的项,在调用require.trace.on
			"loader-inject":0,  // 输出模块加入到应用程序时的信息
			"loader-define":0,
			"loader-exec-module":0,
			"loader-run-factory":0,
			"loader-finish-exec":0,
			"loader-define-module":0,
			"loader-circular-dependency":0,
			"loader-define-nonmodule":0
		},
		async:0,
		waitSeconds:15
	}
);
//>>excludeEnd("replaceLoaderConfig")
网友评论