理解requirejs源码 (二)

tech2022-07-14  160

理解requirejs源码 (二)

require执行的第一个函数执行函数后会创建一个默认的命名空间执行函数后会创建一个新的context对象 require执行的第二个函数执行第二个函数后会生成一个异步的任务异步任务从任务队列中被取出 下面内容根据理解 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函数的作用域
最新回复(0)