理解requirejs源码 (二)
下面内容根据理解 requirejs源码 (一)里面的实例展开
require执行的第一个函数
//Create default context.
req({});
执行函数后会创建一个默认的命名空间
- 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) {...}
}
- 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文件,其他的模块加载机制类似(有时间再丰富)
注意点:
三个函数作用域
- 全局作用域
- (function (global) {}(this) 自执行函数的作用域
- nexContext函数的作用域