这篇文章基于requirejs-2.1.11来分析其加载js文件的流程,而对requirejs其他相关内容不做过多探讨,如有其他方面的需求可参考其规范:http://www.requirejs.cn/或者自行阅读源码。
一般使用requirejs加载js文件有以下两种方法,一种是定义data-main,另一种使用define或require方法来加载。如下所示,使用define方法,传入的第一个参数是我们要加载的依赖,而第二个参数是当所有依赖加载完毕后进行的回调,当然,如果不是定义模块用require即可。先分析第一种然后在详细考察第二种加载方法,估计这两者大同小异。
<script data-main="app.js" src="../lib/require/require-2.1.11.js"></script> define(["angularAMD"], function(angularAMD) { ... }
当requirejs加载完毕后会执行一个立即执行的函数,并且会执行下面这段代码:
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; } }); }
这段代码的主要功能是解析script标签,找出标签中的data-main属性,并且限制最多只找出一个,然后将值也就是要加载的js文件添加到将要下载的任务列表中。下面详细说说其中的逻辑。
isBrowser的定义为 isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document) ,默认情况下为true。
cfg为一个存储配置信息的对象,在执行这段代码之前会检查全局变量requirejs和require是否不为空且为对象,如果是就将他们赋给cfg。所以如若需要配置,只需在执行requirejs之前设置require或requirejs这两个变量即可。这里默认为null,于是就执行到方法内部了。
eachReverse(ary, func)的作用是将func依次作用于第一个参数数组中的每一个元素,如果func返回true则跳出循环,上面这段代码返回的是true,因此只会执行一次。
scripts()即获取当前页面tag为script的元素,返回一个数组,接下来将按照上面所述依次处理这个数组中的元素。
变量head在后面加载js文件时有用,将要加载的js文件以script标签的形式添加到head的子节点中即可。这里如果head未定义,则将当前script的父节点赋给head。
取出'data-main'属性dataMain,在我们的例子中就是'app.js'。根据dataMain设置cfg.baseUrl,这个baseUrl和dataMain在同一目录下。举例,如果dataMain='app.js',那么baseUrl='./',如果dataMain='js/main/app.js',那么baseUrl='js/main/',总之和app.js为同一目录。【有何用?】
然后去掉.js这个后缀。【后面会添加上吗?】
然后通过req.jsExtRegExp.test(mainScript)来判断mainScript是否仍然是个路径,即mainScript中是否包含其中的模式,如果是则将原始路径dataMain赋给mainScript。jsExtRegExp=/^\/|:|\?|\.js$/,其含义如下:【什么样的正常情况会执行这里?】
最后将mainScript添加到cfg.deps这个数组中,接下来看如何处理这个数组中的数据。
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. 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) { context = contexts[contextName] = req.s.newContext(contextName); } if (config) { context.configure(config); } return context.require(deps, callback, errback); };