模块--require.js

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 模块简史

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • 模块 Node 有简单的模块加载系统。在 Node 里,文件和模块是一一对应的。下面例子里,foo.js加载同一个...
    保川阅读 594评论 0 0
  • 原文链接:http://www.cnblogs.com/lvdabao/p/js-modules-develop....
    舌尖上的大胖阅读 701评论 0 1
  • Node.js是目前非常火热的技术,但是它的诞生经历却很奇特。 众所周知,在Netscape设计出JavaScri...
    Myselfyan阅读 4,066评论 2 58
  • 参考资料 Modules/1.0——维基百科CommonJS Modules/1.0——伯乐在线js模块化——博客...
    BeYanJin阅读 3,045评论 0 5
  • 文:骏少 世人皆赞 金陵景 我却叹 华夏悲苦在金陵 哀叹都督周公瑾 大江东去 羽扇纶巾 无奈少主降晋献金陵 哀叹南...
    骏少的宅院阅读 510评论 4 9