理解requirejs源码 (二)


下面内容根据理解 requirejs源码 (一)里面的实例展开

require执行的第一个函数

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

执行函数后会创建一个默认的命名空间

  1. req定义的代码如下
req = requirejs = function (deps, callback, errback, optional) {
        //Find the right context, use default
        var context, config,
            contextName = defContextName;
        // Determine if have config object in the call.
        //deps是一个对象,不是数组和字符串
        if (!isArray(deps) && typeof deps !== 'string') {
            // deps is a config object
            config = deps;
            if (isArray(callback)) {
                // Adjust args if there are dependencies
                deps = callback;
                callback = errback;
                errback = optional;
            } else {
                deps = [];
            }
        }
        if (config && config.context) {
            contextName = config.context;
        }

        context = getOwn(contexts, contextName);
        if (!context) {
            //执行newContext函数时,会创建一个默认的命名空间,contextName的值为下划线(_)
            context = contexts[contextName] = req.s.newContext(contextName);
        }

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

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

上面代码中的contexts保存了所有的命名空间,值为下划线(_)的contextName是其中的一个默认的命名空间;

执行函数后会创建一个新的context对象

newContext函数里面封装了各种工具方法和Module类。
1.工具方法

function trimDots(ary) {...}//处理路径中含有的.和..
function normalize(name, baseName, applyMap) {...}
function removeScript(name) {...}//删除script节点
function hasPathFallback(id) {...}
function splitPrefix(name) {...}
function removeProjectPrefix(name) {...}
function getProjectUrl(parentName) {...}
function buildFinalUrl(fullName,prefix) {...}
//生成一个模块对象
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {...}
//从registry根据模块ID取模块对象,如果取不到新建一个模块
function getModule(depMap) {...}
//这个on方法会调用Module中的on方法
function on(depMap, name, fn) {...}
function onError(err, errback) {...}

2.Module类

Module = function (map) {...}
/* this.exports this.factory
   this.depMaps = [],
   this.enabled, this.fetched
*/
Module.prototype = {
	//初始化模块
	init: function (depMaps, factory, errback, options)  {...}
	//如果一个模块有多个依赖模块,一个依赖模块加载完,depCount值会减1;
	//当模块的所有依赖模块加载完毕后,才通过this.emit('defined', this.exports);触发模块加载完成事件。
	defineDep: function (i, depExports) {...}
	fetch: function () {...}
	load: function () {...}
	check: function () {...}
	callPlugin: function () {...}
	enable: function () {...}
	on: function (name, cb) {...}
	emit: function (name, evt) {...}
}
  1. newContext函数内的context对象,作为newContext的返回值
context = {
	 configure: function (cfg)  {...}
	  makeRequire: function (relMap, options) {...}
	  enable: function (depMap) {...}
	  load: function (id, url)  {...}
	  execCb: function (name, callback, args, exports) {...}
	  onScriptLoad: function (evt)   {...}
	  onScriptError: function (evt) {...}
}
//makeRequire函数是上面context 中定义的函数
context.require = context.makeRequire();

require执行的第二个函数

实例中data-main属性值为config.js,下面源码中的req(cfg)为require执行的第二个函数,执行后会加载config.js

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

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

            //Set final baseUrl if there is not already an explicit one.
            if (!cfg.baseUrl) {
                //Pull off the directory of data-main for use as the
                //baseUrl.
                src = mainScript.split('/');
                mainScript = src.pop();
                subPath = src.length ? src.join('/')  + '/' : './';

                cfg.baseUrl = subPath;
            }

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

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

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

            return true;
        }
    });
}
//cfg为自执行匿名函数内的变量
req(cfg);

执行第二个函数后会生成一个异步的任务

context.nextTick(function () {
	//Some defines could have been added since the
    //require call, collect them.
    intakeDefines();

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

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

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

    checkLoaded();
});

context.nextTick的函数定义如下:

req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
  setTimeout(fn, 4);
 } :

生成的异步任务会放入事件队列中,等待主线程执行(事件轮询机制)

异步任务从任务队列中被取出

req.load = function (context, moduleName, url) {
    var config = (context && context.config) || {},
        node;
    if (isBrowser) {
        //In the browser so use a script tag
        //创建一个script节点
        node = req.createNode(config, moduleName, url);
        node.setAttribute('data-requirecontext', context.contextName);
        node.setAttribute('data-requiremodule', moduleName);
        if (node.attachEvent &&
                !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
                !isOpera) {

            useInteractive = true;

            node.attachEvent('onreadystatechange', context.onScriptLoad);

        } else {
            node.addEventListener('load', context.onScriptLoad, false);
            node.addEventListener('error', context.onScriptError, false);
        }
        node.src = url;
        currentlyAddingScript = node;
        if (baseElement) {
            head.insertBefore(node, baseElement);
        } else {
            head.appendChild(node);
        }
        currentlyAddingScript = null;

        return node;
    } else if (isWebWorker) {
        try {
            importScripts(url);
            //Account for anonymous modules
            context.completeLoad(moduleName);
        } catch (e) {
            context.onError(makeError('importscripts',
                            'importScripts failed for ' +
                                moduleName + ' at ' + url,
                            e,
                            [moduleName]));
        }
    }
};

上面会创建一个script节点放入head节点中,等主线程空闲时会去任务队列中取出来加载js文件,执行其中的代码;

加载完config.js后,会加载main.js文件,其他的模块加载机制类似(有时间再丰富)

注意点:
三个函数作用域

  1. 全局作用域
  2. (function (global) {}(this) 自执行函数的作用域
  3. nexContext函数的作用域
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值