用一个例子读懂 RequireJS
例子来自官方,我稍微改造了一下,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // project.html <! DOCTYPE html> < html > < head > < title >requirejs</ title > <!-- data-main attribute tells require.js to load scripts/main.js after require.js loads. --> < script data-main="scripts/main" src="scripts/require.js"></ script > </ head > < body > </ body > </ html > // scripts/main.js define(function(util){ require("helper/util"); alert('main factory') return {a:1, b:2}; }) // scripts/helper/util.js alert('util.js is loaded!') |
就这样三个文件,运行结果 "util.js is loaded" -> "main factory"。当然,这太简单了,我要说的这才刚开始。
data-main 属性有一个值 "scripts/main",注释也写了,表示当 require.js 加载完之后加载 scripts/main.js。
其实不仅如此,如果指定了这个属性,会把它的目录部分和文件部分拆开,即 scripts/ 和 main,之后会这么做:
cfg.baseUrl = 'scripts/';
cfg.deps = ['main'];
即配置一下 baseUrl 和 deps
接着,用这个配置对象去初始化默认Context,这个过程会判断默认Context 是否依赖别的模块,这里明显依赖 "main",所以需要context.require(it)
怎么做的呢?很简单,它做了两件事:
1. 把需要加载的依赖放进一个数组
2. 遍历该数组,加载依赖,并轮询加载状态
append 的 script 节点需要提一下:
可以看到 RequireJS 为节点加了两个自定义属性,分别表示 contextName 和 moduleName
动态创建的 script 节点当脚本执行完后,会发出onload事件(IE 就是 onreadstatechange),RequireJS 这时会在事件处理函数中进行检测,加载的模块是否同时也存在依赖?来看一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | /** * 这个方法是在 script 节点加载的脚本执行完之后才会执行。 * 1. 通过 event.target 或 event.srcElement 可以取到 script 节点 * 2. 获取节点的 data-requiremodule 属性时,如这里的 "main" * 所以 moduleName 就是 "main" * * @param {String} moduleName */ completeLoad: function (moduleName) { var args; // 队列分为 全局队列 和 context队列 // // 一个被加载的模块,在它的define()中,会把该模块对应的[name, deps, callback]塞进队列 // 如果遵循一个文件一个模块的写法,队列里只有一个元素 // 如果一个文件写了多个模块,那队列里有多个元素 // // 现在的问题是:到底塞进哪个队列呢? // 因为 IE 通过 interactive 状态可以知道当前执行的 script 节点, // 而 script 节点又绑定了 data-requirecontext 属性,所以可以拿到contextName // 综上:IE 加入 context队列,非IE加入 全局队列 // // 这句就表示把全局队列的元素加入context队列,并清空全局队列 // 这样便实现了浏览器的兼容 context.takeGlobalQueue(); // defQueue 即 context 队列 while (defQueue.length) { args = defQueue.shift(); if (args[0] === null ) { // 如果[name, deps, callback]中name为null,即匿名模块 args[0] = moduleName; break ; } else if (args[0] === moduleName) { //Found matching define call for this script! break ; } else { // 如果一个文件出现多个define,才有可能进到这里,暂时可以无视这个分支 callDefMain(args); args = null ; } } // callDefMain其实是main()的apply调用 // 它是定义模块的主函数,通过[name, deps, callback]构造模块 // 它会获取模块需要的依赖,如果是未加载的依赖,会加入context.paused数组 if (args) { callDefMain(args); } else { // 如果加载的文件没有写成模块的形式,进到这里 callDefMain([moduleName, [], null ]); } // 每加载完一个,context.scriptCount就-1 // 对浏览器来说,这没什么问题,但这有一个副作用 // checkLoaded() 会通过scriptCount判断是否要轮询加载状态 // 为了避免这个开销, 这里先-1 if (req.isAsync) { context.scriptCount -= 1; } // 这个方法主要就是处理context.paused,即加载那些依赖 // 并会轮询是否完成加载,并在加载完成时,做一些事 resume(); if (!req.isAsync) { context.scriptCount -= 1; } } |
本文仅作了解,更多的内容我会单独写文章讲解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架