美文网首页我爱编程
模块--require.js

模块--require.js

作者: 209bd3bc6844 | 来源:发表于2017-07-22 16:36 被阅读0次

html文件中的<script>标签中的代码或src引用的js文件中的代码是同步加载和执行的
html文件中的<script>标签中的代码使用document.write()方式引入的js文件是异步执行的
Web动态加载JS外部文件(script标签)
异步加载文件。当页面还有同步代码执行的时候。异步加载从控制台上看到的显示。一直是pending。文件很小的额话其实早就加载回来了。只是js是单线程的。没有功夫去处理显示状态。

面向对象的方式实现require.js

问题:这里都有哪些类型的对象呢?
答案:至少有模块(Module)这一类对象

那模块类对象有哪些数据呢?

Module.id       // 模块id
Module.name     // 模块名字
Module.src      // 模块的真实的uri路径
Module.dep      // 模块的依赖
Module.cb       // 模块的成功回调函数
Module.errorFn  // 模块的失败回调函数
Module.STATUS   // 模块的状态(等待中、正在网络请求、准备执行、执行成功、出现错误……)

又有哪些对应的操作这些数据的方法呢?

Module.prototype.init           // 初始化,用来赋予各种基本值
Module.prototype.fetch          // 通过网络请求获取模块
Module.prototype.analyzeDep     // 分析、处理模块的依赖
Module.prototype.execute        // 运算该模块

先想一下require.js是怎么使用的。
index.html页面引入
<script type="text/javascript" src="./require.js" data-main="main"></script>
data-main 是主文件的入口。main.js里就是主文件,里面有require。然后其他js文件就是定义模块,define()。
假设main.js现在如下

require(['a', 'b'], function (a, b) {
    a.hi();
    b.goodbye();
}, function () {
    console.error('Something wrong with the dependent modules.');
});

这个文件执行的时候。我们先要去加载依赖的a和b模块。我们既然用Module对象来描述每一个模块对象。一般构造函数都如下。有个init

 function Module(name, dep, cb, errorFn) {
        this.init(name, dep, cb, errorFn);
 }

所以我们在解析main.js时候就分别对a和b实例化。只是这时候的实例化的主要目的是执行Module上的加载js函数。
如何异步加载js文件呢。这里用的是动态生成script标签。新的<script>元素加载js文件。此文件当元素添加到页面之后立刻开始下载。等到没有同步代码执行时候。立刻执行加载好的js文件。
当执行加载好的a.js文件时候,就会执行define函数。这里我们队刚刚实例化的a模块进行丰富。

    let module = modules[name];
    module.name = name;
    module.dep = dep;
    module.cb = cb;
    module.errorFn = errorFn;

如果define函数还依赖别的模块。要继续去加载别的模块。
碰到了一个难点:如何分析和处理模块的依赖?
举个例子:main.js必须等a和b模块都加载执行完成后才能执行main的回调。
我想了一个方法:记数法。分两步走。

  1. Module原型新增Module.depCount属性,初始值为该模块依赖模块数组的长度。
  2. 假如depCount===0,说明该模块依赖的模块都已经运算好了,通过setter触发执行该模块。
  3. 某模块执行成功之后,触发下一步。
  4. 下一步为:通过对象mapDepToModuleOrTask,查找到依赖与该模块的所有模块,那么让那些模块都执行depCount--

注:对象mapDepToModuleOrTask的作用是映射被依赖模块到依赖模块之间的关系。
结构如下图所示。举个例子:当模块a准备好之后,我们就遍历mapDepToModule['a']对应的数组,里面的每一项都执行depCount--。
分析依赖模块这个方法

Module.prototype.analyzeDep = function () {
    let depCount = this.dep ? this.dep.length : 0;// 依赖的模块数
    if (depCount === 0) {//如果不依赖别的模块,直接执行回调。
      this.execute();//执行模块回调
      return;
    }
    Object.defineProperty(this, 'depCount', { // 如果依赖别的模块,就增加一个depCount的属性。当依赖加载完一个depCount就--。知道depCount=0。触发回调函数
      get() {
        return depCount;
      },
      set(newDepCount) {
        depCount = newDepCount;
        if (newDepCount === 0) {
          this.execute();
        }
      }
    });
    this.dep.forEach((depModuleName) => { // 遍历该模块的依赖模块,再加载依赖模块
      if (!modules[depModuleName]) { // 映射所有依赖该(depModuleName)模块的模块
        let module = new Module(depModuleName);
        modules[depModuleName] = module;
      }
      if (!mapDepToModuleOrTask[depModuleName]) {
        mapDepToModuleOrTask[depModuleName] = [];
      }
      mapDepToModuleOrTask[depModuleName].push(this); //当前模块的依赖模块都push当前模块
    });
  }

处理依赖循环

我们有时候会定于循环依赖的模块,比如a需要b并且b需要a,会造成死循环(可以在代码中判断是循环依赖的话a需要b,b有需要a。在加载b模块de时候。不再去加载a模块)。这样就不会造成死循环了,但是在这个情况下当b模块调用时他将会从a获得一个undefined值。所以解决办法是模块b的回调函数中,并不能直接引用到a,需要使用require方法包住。
处理办法

// a.js
define(['b'],function (b) {
    var hi = function () {
        console.log('hi');
    };

    b.goodbye();
    return {
        hi: hi
    }
});
// b.js
define(['require', 'a'], function (require) {
    var goodbye = function () {
        console.log('goodbye');
    };
    // 因为在运算b的时候,a还没准备好,所以不能直接拿到a,只能用require再发起一次新的任务
    require(['a'], function (a) {
        a.hi();
    });

    return {
        goodbye: goodbye
    }
});

这样一来原先的require.js就有问题了
原先的设计中, 每一个define是跟一个模块一一对应的, require只能用一次,用于主入口模块(如:main.js)的加载。现在define中还需要解析require,require也需要解析依赖,执行回调。所以require也应当是一个模块。但这个模块不需要fetch。我将它命名为:任务(Task),这是一个有别于Module的新的类。
每一次调用require,相当于新建一个Task(任务)。这个任务的功能是:当任务的所有依赖都准备好之后,执行该任务的成功回调函数。
有没有发现这个Task原型与Module很像?它们都有依赖、回调、状态,都需要分析依赖、执行回调函数等方法。但是又有些不同,比如Task没有网络请求,所以不需要fetch这样的方法。
所以,我让Task继承了Module,然后重写某些方法。
作者的博客--实现require
作者的代码
我仿照写的代码
JavaScript 模块简史

相关文章

  • 我的前端模块化

    require.js的使用的步骤 如何定义模块?如何引入模块? ​ 引入插件: script引入定义插件: 用...

  • 模块--require.js

    html文件中的 标签中的代码或src引用的js文件中的代码是同步加载和执行的html文件中的 标签中的代码使用d...

  • 学习require.js(转)

    前言 提到require.js大多数人会说提到模块化开发,AMD等等,其实require.js并没有这么多复杂的概...

  • 春哥教你前端模块化

    前端模块化 (Require.js) 为什么要用 前端模块化 早期,所有Javascript代码都写在一个文件里面...

  • Webpack基础(一)

    模块打包工具,可压缩,优化代码格式支持大部分模块规范:COMMONJS、AMD(require.js)、CMD(s...

  • webpack(exclude存疑)

    参考资料 webpack漫谈 webpack专栏 js早期引入模块的方式 script标签 Require.js ...

  • js深入(require.js与common.js)

    参考资料 require.js(amd) 异步请求模块(适用于浏览器) common js 同步请求模块(适用于n...

  • app 2018-10-25

    1. require.js的由来 使用: 从最原始的模块到require的进化 模块:实现特定功能的一组方法 ,...

  • 关于模块化编程

    相关知识 前端模块化阮一峰三篇日志 模块的写法 AMD规范 require.js的用法 如果我没有理解错的话,...

  • AMD/RequireJS 使用入门

    参考资料 RequireJS 中文网Javascript模块化编程(三):require.js的用法——阮一峰 前...

网友评论

    本文标题:模块--require.js

    本文链接:https://www.haomeiwen.com/subject/xvsbcxtx.html