模仿RequireJs的用法实现一个低配版的模块加载器

Contents

  1. 前言
  2. 回顾RequireJs的基本用法
  3. 实现原理
  4. 使用方法
  5. 总结

前言

前段时间一直想用单页开发技术写一个本身的我的网站(使用es2015),写了一部分以后,发现单页应用由于只有一个页面,因此第一次加载index.html时就要下载全部js文件,而且为了好管理各个部分的状态,须要划分页面的各个功能区为各个模块,es2015自己是不支持一些模块规范的(好比AMD、CMD、CommonJs等),因此只能这样模拟实现:javascript

// global
  var spa = (function(){...})();

  // module blog
  spa.blog = (function(){
    ...
    return {
      do1: do1,
      do2: do2,
    };
  })();

  // module model
  spa.model = (function(){...})();

  // module shell
  spa.model = (function(){...})();

而且各个模块之间又存在一些依赖关系,在index.html里面写script标签来载入模块时须要写不少个,同时也要根据依赖关系来肯定书写顺序,页面逻辑混乱,以下:html

<script type="text/javascript" src="/javascripts/spa.utils.js"></script>
  <script type="text/javascript" src="/javascripts/spa.model.js"></script>
  <script type="text/javascript" src="/javascripts/spa.mock.js"></script>
  <script type="text/javascript" src="/javascripts/spa.chat.js"></script>
  <script type="text/javascript" src="/javascripts/spa.blog.js"></script>
  <script type="text/javascript" src="/javascripts/spa.action.js"></script>
  <script type="text/javascript" src="/javascripts/spa.shell.js"></script>

以前用过RequireJs(一个流行的JavaScript模块加载器),它是用同构js的架构来写的,因此node.js环境下也能使用。我想本身能够尝试一下写一个低配版的js模块加载器 requireJs-nojsja 来应付一下我这个单页网站,固然只是大体模仿了主要功能。java

回顾RequireJs的基本用法

1. 配置模块信息

requirejs.config({
          baseUrl: '/javascripts',  // 配置根目录
          paths: {
            moduleA: 'a.js',
            moduleB: 'b.js',
            moduleC: 'c.js',
          },
          shim: {  // 配置不遵循amd规范的模块
            moduleC: {
              exports: 'log',
              deps: ['moduleA']
            }
          },
      });

2. 定义一个模块

define(name, ['moduleA', 'moduleB'], function(a, b){
    ...
    return {
      do: function() {
        a.doSomething();
        b.doAnother();
      }
    };
  });

3. 引用一个模块

// 引用模块
  require(['moduleA', 'moduleB'], function(a, b) {
    a.doSomething();
    b.doAnother();
  });

实现原理

1. config方法肯定各个模块的依赖关系

/* 记录模块访问地址和模块的依赖等信息 */
  Require.config({
    baseUrl: '/javascripts/',
    paths: {
      'moduleA': './moduleA.js',  // 相对于当前目录
      'moduleB': '/javascripts/moduleB.js',  // 不使用baseUrl
      'moduleC': 'moduleC.js',

      'moduleD': {
        url: 'moduleD.js',
        deps: ['moduleE', 'moduleF'],
      },
      ...
    },
    shim: {
      'moduleH': {
        url: 'moduleH.js',
        exports: 'log',
      },
    }
  });

2. 数据请求请求过程分析

(1) config配置模块信息时并不会触发网络请求
(2) 在index.js主入口文件里使用require方法引用多个模块时,根据config配置文件构造一下全部模块的依赖分析树。按深度优先或是广度优先来遍历这个依赖树,将全部依赖按照依赖顺序放进一个数组,最后进行数组去重处理,由于会出现依赖重复的状况node

var dependsTree = new Tree('dependsTree');
  var dependsArray = [];
  var dependsFlag = {};  // 解决循环依赖

  // 建立树
  setDepends(depends, dependsTree);
  // 获得依赖数组
  sortDepends(dependsArray, dependsTree);
  // 数据去重
  arrayFilter(dependsArray);

  return dependsArray;

(3) 建立XHR对象异步下载数组里面的全部js文件,按照依赖顺序挨个解析js代码,解析完成后触发回调函数,回调函数里传入各个模块的引用git

// ajax下载代码文件
  Utils.request(url, 'get', null, function(responseText){
    // 暂时保存
    _temp[module_name] = responseText;
  });
    
  // 文件下载完成后eval解析代码
  array.map(function(jsText){
    ...
    eval(jsText);
    ...
  });
    
  // 调用回调函数
  callback.apply(null, [dep1, dep2, dep3]);

使用方法

详细说明: github README.mdgithub

总结

  1. 下载js代码时我用了ajax来实现,因此对于跨域文件和CDN会有点问题,这个能够改为建立script标签,指定标签src,最后将document.head.appendChild(script),这样来解决,其它的诸如使用XMLHttpRequest 2.0,iframe等也能够的,能够实验一下。
  2. 解析代码时我用了eval的方法,这个eval在JavaScript里面是众说纷纭,能够看看这个,若是是用了上面建立script标签的方法的话,就不用本身eval了。
  3. 发现一个bug,存在循环依赖时,代码会报错,还没去解决。RequireJs是这样处理的:模块a依赖b,同时b依赖a,这种状况下b的模块函数被调用时,被传入的a是undefined,因此须要本身在b里面手动require一下a。
相关文章
相关标签/搜索