美文网首页我爱编程
模块--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

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