june01 模块加载工具制造指南
In 未分类 on 2017年01月10日 by view: 6,617
4

前言

看到这个标题,估计有同学会想,又要重复造轮子么?其实重复造轮子在大多数情况下确实是不太可取的,既浪费了精力又浪费了时间。但这并不能说明重复造轮子完全不可取,比如你想要某个轮子的精简版,又比如你想学习某个轮子的制造方法,重复造轮子也可以是有意义的。

简介

接下来,我们就来学学某个轮子简易版制造方法,这个轮子就是模块加载工具。

说起模块加载工具,估计大家就会想起 webpack、commonjs 等,更“ 久远” 一点的会想起 requirejs 和 seajs。这些工具都源于前端的模块化思想。

为什么前端需要模块化?这主要得益于前端技术的发展,使得前端不再像以前那样只能展示一下静态内容,撑死加上几个飞来飞去的动画。现在的前端内容越来越丰富,我们可以播放视频,可以协同工作,还可以玩游戏。这就导致了前端代码量剧增。当代码行数噌噌噌往上涨时,模块化思想就自然而然地出来了。

对于前端来说,最简单的模块化就是拆分成多个文件,然后在 html 里就会出现如下的代码:

各位有没有觉得这种代码有点儿难看?像这样的代码不止难看,依赖也不清晰,假如上面的 module_b 只是因为 module_a 的需要才引入的,那么当我们去掉 module_a 时还得搜一下相关文档或者源码,当我们检索出确确实实只有 module_a 才依赖了 module_b,我们才敢放心的把 module_b 给去掉。

因此,就衍生了像 requirejs 之类模块加载工具,同时还能处理依赖关系。其实像 requirejs 和 webpack 之类的构建工具处理模块化时很相似,只是处理模块依赖的时机不同,requirejs 是直接在浏览器里处理,而 webpack 则是在上线前就将模块进行打包。而在代码上两者最大的差异就是,requirejs 需要每个模块包裹一层依赖代码(其实这层代码也可以借由构建工具生成),而 webpack 则会在打包后的代码里注入一下模块化的脚本。事实上这两者也不是水火不容,这主要看项目的技术选型。

说了那么多,接下来就来进入正题,我们这次就是来造一个简易版的类似 requirejs 的模块加载工具,注意是简易版,所以这个轮子最好不要直接投入到生产环境中,造这个轮子更多的目的是为了一起学习 XD。

需求

使用方式我们就做得简单一点,只暴露一个方法出来:define 方法。

当我们需要定义一个模块时,可以像如下方式编写代码:

每个模块都用 define 来定义,声明依赖的模块和回调方法。回调中可以返回一个对象,也可以不返回值。如果返回对象则会被注入到依赖这个模块的模块回调方法中,如果不返回值则注入空对象。同时依赖的模块可以是纯文本文件或 json 文件,如果纯文本,注入进来的会是该文件的字符串内容,如果是 json 文件则注入 json 对象:

设计与思考

我们这里有如下几个问题需要思考一下:

  • 如何注入依赖?
  • 如何获取依赖模块的绝对路径?
  • 如何加载依赖的模块?
  • 如何处理循环依赖?

针对这几个问题我们来对这个模块加载工具进行设计。

如何注入依赖?

需要注入依赖到当前模块,就得保证依赖是先于当前模块加载并执行完,这样我们就需要维护一个模块队列,保证模块加载的顺序和保存模块的状态。

当遇到 define 方法进行模块定义时,先获取依赖,将依赖的加载顺序置于当前模块之前,这个我们通过维护一个模块的状态列表就可以达成。状态设计成以下三种:

  • LOADING:模块正在加载中。
  • WAITING:模块已经加载完毕,正在等待依赖模块加载。
  • DEFINED:模块和其依赖均已经加载完毕,并且执行过回调,完成模块定义。

每次定义模块,我们就检查该模块所依赖的模块状态,如果依赖都已定义,则进入执行回调阶段;如果依赖未完全就位,则设置为等待中,将未加载的模块放入加载列表进行加载。具体流程如下: