Better

Ethan的博客,欢迎访问交流

动手实现模块加载器

众所周知,模块加载器的代表有require.js和sea.js,随着ES6的普及,这两个元老也即将退出舞台,但是理解模块加载的远离才是很重要的,旨在动手实现一个模块加载器, 从而理解原理,也加深自己的基础!代码上传到 github 库中,因此这里简单总结下思路,源码和更多请看其他!

AMD 规范

通过 define 函数定义模块,同时可以引入依赖,通过 return 对象将函数暴露出来。

通过 config 函数自定义 baseUrl 和 paths。

通过 use 函数引入模块,并且得到返回的函数。

基本功能

如何加载 JavaScipt 文件呢!其实并不是用了什么高级的方法,只不过通过 js 动态拼接 script 标签,但是远程文件是异步加载的,如何知道加载完成呢?监听 script dom 对象的 onload 事件即可。

知道如何加载文件,接下来我们理解一下 define 函数的功能。在了解 define 函数之前,我们需要知道一个模块可能同时被多个模块加载,因此我们需要保证同意不重复加载,但是加载完成后,注册该模块加载完成的回调函数需要一次执行。

我们创建 modMap 对象,存储模块的加载情况,目的就是防止重复加载,如果加载完毕,就无需加载。

madMap 对象增加模块属性有两个时机

  1. 作为 use 的依赖时
  2. define 定义时

弄清楚 define 函数的执行时机是在文件加载完毕时自动执行的。modMap 主要存储模块的如下属性:

  1. deps 依赖
  2. status 状态
  3. callback 回调杉树
  4. oncomplete 事件监听数组
  5. exports 缓存模块结果

接下来聊聊 use 函数的原理

  1. 依次加载 deps 依赖模块
  2. 存储每个模块返回的值,所有模块加载完毕后,执行回调函数即可

接下来聊聊几个内部函数 loadMod、execMod、execComplete

  • loadMod
    • 判断模块的状态,不存在的加载,存在则将回调函数加入 oncomplete 中
    • 需要考虑到加载的目标模块,可能依赖其他模块
  • execMod
    • 执行模块,得到最终结果
    • 将模块结果缓存到模块对应的 exports 属性
  • execComplete
    • 模块的最终结果,遍历执行所有的监听

路径解析

我们知道,现实项目中,文件不可能都在根目录下,都是分散在不同的文件夹中,甚至我们加载的有可能是远程文件,因此我们需要允许路径可配置。我们新增 config 函数。主要功能

  1. 配置 baseUrl
  2. paths

新增 outputPath 和 replaceName 内部函数

  • outputPath
    • 负责针对不同情况,计算绝对路径
  • replaceName
    • 如果在 paths 中配置了路径,需要做路径替换

匿名模块

我们的AMD模块加载器基本已经能够使用了,但是还不够,因为我们没有允许匿名模块,以及没有依赖等情况。实际上在 amd 的规范中规定的就是 define 函数的前两个参数是可选的,当没有id(模块名)的时候也就意味着不会有模块依赖于这个模块。

因此我们只需要修改 define 函数,对 id 和 deps 做非空判断即可。

其他



留言