js实现简易模块加载器

前言

前一段时间分析过require.js源码,整体的分析有些泛泛而,谈内容有些空洞,没有把握住requirejs依赖处理的精髓, 这是分析require.js源码最为失败的地方。

虽然没有真正的理解其实现细节,但是对其源码组织以及基本的逻辑执行有了整体的了解。

本文是参考网上的源码,分析其思想实现的简易的模块加载器,旨在加深对于require.js的认知与理解。

实现思路

首先明确的几点如下:

  • 每调用一次require函数,就会创建一个Context对象

  • Module对象表示模块对象,基本的操作都是该对象的原型方法

上面两个对象是实现该简易模块加载器核心,加载过程中的处理流程如下图所示:
加载流程
加载流程代码

Module对象的属性有:

mid:表示模块id
src:模块路径
name: 模块名称
deps:模块的依赖列表
callback:回调函数
errback:错误处理函数
status:模块状态
exports:与回调函数参数序列对应的模块输出

Module的原型对象有如下几个方法:

init:Module对象的初始化处理
fetch:创建script节点并追加到元素节点上
checkCycle:处理循环依赖,返回当前的循环依赖列表
handleDeps:处理依赖列表
changeStatus:改变模块的状态,主要处理模块是否加载成功
execute:依赖的模块都加载成功后,参数列表的获取

如何处理依赖列表的

事实上,处理依赖列表是define以及require中处理,define函数以及require函数的处理代码如下:
define及require

首先看require函数,实例:

require(['a', 'b'], function(a, b) {
	console.log(a, b);
});

从上面可以看出,调用require函数,依赖列表是[‘a’, ‘b’],回调函数是function(a, b) {console.log(a, b);}

从require代码可以就看出,调用Context构造函数创建一个Context对象,现在看下Context构造函数的处理:

let Context = function(deps, callback, errback) {
	this.cid = ++contextId;
	this.init(deps, callback, errback);
};

Context.prototype.init = function(deps, callback, errback) {
	this.deps = deps;
	this.callback = callback;
	this.errback = errback;
	contexts[this.cid] = this;
};

上面中重要的是contexts[this.cid] = this;将当前的Context对象注册到全局contexts对象集合中。

然后调用handleDeps函数,该函数处理依赖列表,具体的代码如下:

handleDeps: function() {
    let depCount = this.deps ? this.deps.length : 0;
    // require.js中处理循环依赖的处理
    let requireInDep = (this.deps || []).indexOf('require');
    if (requireInDep !== -1) {
        depCount--;
        this.requireInDep = requireInDep;
        this.deps.splice(requireInDep, 1);
     }
     // 处理循环依赖情况
     let cycleArray = this.checkCycle();
     if (cycleArray) {
         depCount = depCount - cycleArray.length;
     }
     // depCount表示当前模块的依赖模块数,depCount为0表示模块中某一依赖加载完成
     this.depCount = depCount; 
     if (depCount === 0) {
         this.execute();
         return;
     }
     // 遍历依赖列表,创建Module对象,并且将当前模块与其依赖的关系构建出来maps
     this.deps.forEach((depModuleName) => {
          if (!modules[depModuleName]) {
              let module = new Module(depModuleName);
              modules[module.name] = module;
          }
          if (!maps[depModuleName]) {
               maps[depModuleName] = [];
          }
          maps[depModuleName].push(this);
       });
}
循环依赖的处理

本次实现代码中的循环依赖的处理,是require.js中官方的方法,就是传递require在回调函数中再次require,为什么这样就可以解决循环依赖?

就本次实现而言,因为require一次就会创建一个Context对象。主要代码如下:

// require.js中处理循环依赖的处理
let requireInDep = (this.deps || []).indexOf('require');
if (requireInDep !== -1) {
    depCount--;
	this.requireInDep = requireInDep;
	this.deps.splice(requireInDep, 1);
 }
 // 获取循环依赖
 let cycleArray = this.checkCycle();
 if (cycleArray) {
      depCount = depCount - cycleArray.length;
 }

// execute函数中代码
// 插入require到回调函数的参数列表中
if (this.requireInDep !== -1 && this.requireInDep !== undefined) {
     arg.splice(this.requireInDep, 0, require);
}
结束语

纸上得来终觉浅,觉知此事要躬行,通过实现简易的模块加载器,对于require.js模块加载的思想以及逻辑处理更加地清晰。

虽然与require.js对于js文件的异步加载的处理方式不同,但是本质是一样的,require.js是添加script节点到head标签中,并且script添加async属性来实现异步加载的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值