众所周知,模块加载器的代表有require.js和sea.js,随着ES6的普及,这两个元老也即将退出舞台,但是理解模块加载的远离才是很重要的,旨在动手实现一个模块加载器, 从而理解原理,也加深自己的基础!代码上传到 github 库中,因此这里简单总结下思路,源码和更多请看其他!
AMD 规范
通过 define 函数定义模块,同时可以引入依赖,通过 return 对象将函数暴露出来。
通过 config 函数自定义 baseUrl 和 paths。
通过 use 函数引入模块,并且得到返回的函数。
基本功能
如何加载 JavaScipt 文件呢!其实并不是用了什么高级的方法,只不过通过 js 动态拼接 script 标签,但是远程文件是异步加载的,如何知道加载完成呢?监听 script dom 对象的 onload 事件即可。
知道如何加载文件,接下来我们理解一下 define 函数的功能。在了解 define 函数之前,我们需要知道一个模块可能同时被多个模块加载,因此我们需要保证同意不重复加载,但是加载完成后,注册该模块加载完成的回调函数需要一次执行。
我们创建 modMap 对象,存储模块的加载情况,目的就是防止重复加载,如果加载完毕,就无需加载。
madMap 对象增加模块属性有两个时机
- 作为 use 的依赖时
- define 定义时
弄清楚 define 函数的执行时机是在文件加载完毕时自动执行的。modMap 主要存储模块的如下属性:
- deps 依赖
- status 状态
- callback 回调杉树
- oncomplete 事件监听数组
- exports 缓存模块结果
接下来聊聊 use 函数的原理
- 依次加载 deps 依赖模块
- 存储每个模块返回的值,所有模块加载完毕后,执行回调函数即可
接下来聊聊几个内部函数 loadMod、execMod、execComplete
- loadMod
- 判断模块的状态,不存在的加载,存在则将回调函数加入 oncomplete 中
- 需要考虑到加载的目标模块,可能依赖其他模块
- execMod
- 执行模块,得到最终结果
- 将模块结果缓存到模块对应的 exports 属性
- execComplete
- 模块的最终结果,遍历执行所有的监听
路径解析
我们知道,现实项目中,文件不可能都在根目录下,都是分散在不同的文件夹中,甚至我们加载的有可能是远程文件,因此我们需要允许路径可配置。我们新增 config 函数。主要功能
- 配置 baseUrl
- paths
新增 outputPath 和 replaceName 内部函数
- outputPath
- 负责针对不同情况,计算绝对路径
- replaceName
- 如果在 paths 中配置了路径,需要做路径替换
匿名模块
我们的AMD模块加载器基本已经能够使用了,但是还不够,因为我们没有允许匿名模块,以及没有依赖等情况。实际上在 amd 的规范中规定的就是 define 函数的前两个参数是可选的,当没有id(模块名)的时候也就意味着不会有模块依赖于这个模块。
因此我们只需要修改 define 函数,对 id 和 deps 做非空判断即可。